--- Author: Kristof Vandam Categories: - development Date: 2018-08-29T22:44:46+02:00 Draft: false Tags: - vue - vuejs - prism - prismjs - hugo - javascript - js - json Title: Live Search With HUGO Truncated: true --- HUGO is static, that's a fact. How can I implement a live search? Searching the internet provided me only solutions that require a page refresh, this time of age performance is key, so that's why I wanted a fast and fuzzy search implementation. ## Research Some this I found which helped to get there are: * [here](https://gohugo.io/tools/search/) *more specific* [here](https://gist.github.com/eddiewebb/735feb48f50f0ddd65ae5606a1cb41ae) * https://vuejs.org/ * http://fusejs.io/ * https://momentjs.com/ * https://github.com/axios/axios ## Create a JSON object containing all articles Actually every data you want to search, in this guide (and on this website) I use the following data: 1. Title 2. Date 3. Author 4. Tags 5. Content This is specified in a custom *layout*. Note the ```(dict "title" ...)``` line. You can add any data that HUGO processes (for each article). Its a list of key/values, the keys are presented between the quotes, the value as first value. **layouts/json/single.html** ```.language-none.line-numbers {{- $.Scratch.Add "index" slice -}} {{- range where .Site.Pages "Type" "not in" (slice "page" "json") -}} {{- $.Scratch.Add "index" (dict "title" .Title "date" .Date "author" .Params.author "href" .Permalink "tags" .Params.tags "content" .Plain) -}} {{- end -}} {{- $.Scratch.Get "index" | jsonify -}} ``` Now, with this file in place the next thing to do is to create a content page, where this layout is used. This file triggers the creation of "index.json". **content/search.md** ```.language-yaml.line-numbers --- date: "2017-03-05T21:10:52+01:00" type: "json" url: "index.json" --- ``` **Example of the data returned** *You can checkout the json object for this website, just go to* https://hagfi.sh/index.json ```.language-json.line-numbers [ { "author": "Kristof Vandam", "content": "HUGO is static, that\u0026rsquo;s a fact. How can I implement a live search? Searching the internet provided me only solutions that require a page refresh, this time of age performance is key, so that\u0026rsquo;s why I wanted a fast and fuzzy search implementation. Research Some this I found which helped to get there are:\n https://gohugo.io/tools/search/ ", "date": "2018-08-29T22:44:46+02:00", "href": "http://localhost:1313/development/live-search-with-hugo/", "tags": null, "title": "Live Search With HUGO" } ] ``` ## Add the required dependencies (we use CDN's) Make sure the following dependencies are loaded between the head tags. We use a little trick to let the browser decide if http or https is used. These are called *Protocol-Relative URL's*. ```.language-markup.line-numbers ``` ## Add the actual search logic It's a best practice to add the JavaScript right before the closing body tags. I highly suggest checking out VueJS with Webpack, but in this case a some simple JS inside script tags will do just fine. I will go over each section to clarify. ```.language-javascript.line-numbers var app = new Vue({ el: '#app', data: { fuse: null, search: "", result: [], index: [] }, mounted() { let self = this let options = { shouldSort: true, threshold: 0.6, location: 0, distance: 100, maxPatternLength: 32, minMatchCharLength: 1, keys: [ "title", "author", "date", "content" ] } axios.get('/index.json') .then(function (response) { self.index = response.data self.fuse = new Fuse(response.data, options); self.result = fuse.search(""); }) .catch(function (error) { console.log(error) }) }, watch: { search(nval, oval) { if (nval.length > 0) { this.result = this.fuse.search(nval) } else { this.result = [] } } } }) ``` ### Create the Vue instance When creating a new Vue instance we assign Vue to a DOM element, most of the time an ID on your body tag is used. ```.language-javascript.line-numbers var app = new Vue({ el: '#app', ... }) ``` ### Create a data object This object is accesible across your DOM and Vue instance. Inside functions you can reffer to these with ```this.*```.language- I initiated some variables like 'fuse' so it can be used inside *watch* and *methods*. ```.language-javascript.line-numbers data: { fuse: null, search: "", result: [], index: [] }, ``` ### What todo when everything is ready The ```mounted()``` function is triggered when everything ready to start processing your custom code. *(This function used to name 'ready()')*. We assign ```this``` to ```self``` to handle some scope issues in the axios promise. We polulate some options for FuseJS, note that the keys array is important here. Here we specify which keys of our index.json we want to search. The index.json file is loaded with AJAX, this way the page should not wait for content that is not required immediately. When axios retrieves the date we create a Fuse instance (assigned to ```self.fuse``` (or ```this.fuse```)). ```.language-javascript.line-numbers mounted() { let self = this let options = { shouldSort: true, threshold: 0.6, location: 0, distance: 100, maxPatternLength: 32, minMatchCharLength: 1, keys: [ "title", "author", "date", "content" ] } axios.get('/index.json') .then(function (response) { self.index = response.data self.fuse = new Fuse(response.data, options); self.result = fuse.search(""); }) .catch(function (error) { console.log(error) }) }, ``` ### When something is entered inside the search field We watch for ```this.search``` to change, if it changes this function is called. Remember we set ```search: ""``` inside our data object? If the 'nval' (New VALue) is larger than 0 characters we trigger the search function of fuse, which will return a new data set, but filtered. This dataset is stored inside ```this.result```.language- If the length of 'nval' changes to 0 characters we hardcode the result to be an empty array (to prevent possible edgecases). ```.language-javascript.line-numbers watch: { search(nval, oval) { if (nval.length > 0) { this.result = this.fuse.search(nval) } else { this.result = [] } } } ``` ### Ok, cool, now how do I showcase the results? Well, it's up to you. The most important parts in this example are: 1. Bind ```this.search``` to the input field (with ```v-model```) 2. Loop through ```this.result``` with ```v-for```, it will recreate the li tag 'for each' result item. 3. Use the result item, reffered as ```r```. 4. Links are extracted from the result item by the 'href' key and bound to the href attribute. ```:href="r.href"``` We use Moment.js to format the default (can be changed) HUGO date format to 'D' (Day), 'MMM' (Month, max 3 characters), 'YYYY' (Full Year). ```.language-markup.line-numbers
```