Autocomplete Input with Debounce

Video

JavaScript Notes

HTML
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Debouncing</title>
        <meta name="viewport" content="width=device-width">
        <style>
            
        </style>
    </head>
    <body>
        <h1>Heading</h1>
        <form>
            <input type="search" id="txt-search" />
        </form>
        
        <p id="output"></p>
        <ul id="matches"></ul>
        
        <h3>Full List</h3>
        <pre>'apple', 'acorn', 'bee', 'beet', 'beef', 'bunny', 'cookie', 
        'corn', 'corndog', 'dog', 'dogma', 'echo', 'elephant'</pre>
        
        <script>
            const KEY = 'debounce-terms';
            
            let init = function(){
                //document.getElementById('txt-search').addEventListener('input', search);
                document.getElementById('txt-search').addEventListener('input', efficientSearch);
                
                let terms = ['apple', 'acorn', 'bee', 'beet', 'beef', 'bunny', 'cookie', 
                             'corn', 'corndog', 'dog', 'dogma', 'echo', 'elephant'];
                localStorage.setItem(KEY, JSON.stringify(terms));
            }
             
            let search = function(ev){
                let text = ev.target.value;
                document.getElementById('output').textContent = `List Matching ${text}`;
                let ul = document.getElementById('matches');
                
                //call an asynchronous search to match what has been typed
                getList(text)
                .then((list)=>{
                    ul.innerHTML = '';
                    if( list.length == 0){
                        let li = document.createElement('li');
                        li.textContent = "NO MATCHES";
                        ul.appendChild(li);
                    }else{
                        list.forEach(item=>{
                            let li = document.createElement('li');
                            li.textContent = item;
                            ul.appendChild(li);
                        })
                    }
                })
                .catch(err=>console.warn(err));
            }
            
            let getList = function(txt){
                return new Promise((resolve, reject)=>{
                    //use setTimeout with random value to show what can happen
                    let r = Math.floor(Math.random()*1000);
                    setTimeout((function(){
                        let t = '^' + this.toString();
                        let pattern = new RegExp(t, 'i'); //starts with t
                        let terms = JSON.parse(localStorage.getItem(KEY));
                        let matches = terms.filter(term => pattern.test(term));
                        console.log('matches', matches);
                        resolve(matches);
                    }).bind(txt), r);
                })
            }
            
            let debounce = function(func, wait, immediate) {
                var timeout;
                return function() {
                    var context = this, args = arguments;
                    var later = function() {
                        timeout = null;
                        if (!immediate) func.apply(context, args);
                    };
                    var callNow = immediate && !timeout;
                    clearTimeout(timeout);
                    timeout = setTimeout(later, wait);
                    if (callNow) func.apply(context, args);
                };
            };
            
            let efficientSearch = debounce(function(ev){
                let text = ev.target.value;
                document.getElementById('output').textContent = `List Matching ${text}`;
                let ul = document.getElementById('matches');
                
                //call an asynchronous search to match what has been typed
                getList(text)
                .then((list)=>{
                    ul.innerHTML = '';
                    if( list.length == 0){
                        let li = document.createElement('li');
                        li.textContent = "NO MATCHES";
                        ul.appendChild(li);
                    }else{
                        list.forEach(item=>{
                            let li = document.createElement('li');
                            li.textContent = item;
                            ul.appendChild(li);
                        })
                    }
                })
                .catch(err=>console.warn(err));
            }, 300);
            //call the debounced function at most once every 300ms
               
            document.addEventListener('DOMContentLoaded', init);
            
            //debounce function - thanks to David Walsh
            //https://davidwalsh.name/javascript-debounce-function
            //who took this from underscore.js
        </script>
    </body>
    </html>