autocomplete snippets

How to create an autocompleted field with jQuery, which looks and feels like autocomplete with Scriptaculous & prototype

Tagged jquery, autocomplete, plugin, scriptaculous, prototype  Languages javascript

There are a lot of autocomplete plugins for jQuery, but not one of them seems to work similarly to Scriptaculous, except for the one at bassistance.de.

To use it follow the instructions found here.

There are a couple of gotchas for ex-scriptaculous users, like that the controller should return the list of tags with a newline character as separator, compared to an HTML list with Scriptaculous.

The other gotcha is the syntax, which is a lot prettier than Scriptaculous:

<script type="text/javascript">
$("#tags-field").autocomplete("/tags/autocomplete", {
    multiple: true,
    autoFill: true,
    minChars: 3
});
</script>

Remember that /tags/autocomplete should render a list of tags delimited by linefeeds:

ruby
php
java
perl
python

Note that jQuery sends the user typed text in a request parameter named 'q', so jQuery makes the following request to your controller: '/tags/autocomplete?q=ruby'.

The list that's shown when you type something can be styled with these CSS rules:

div.ac_results {
    width: 350px;
    background: #fff;
}

div.ac_results ul {
    border:1px solid #888;
    margin:0;
    padding:0;
    width:100%;
    list-style-type:none;
}

div.ac_results ul li {
    margin:0;
    padding:3px;
}

// Hovering over list item
div.ac_results ul li.ac_over {
    background-color: #ffb;
}

The plugins documentation is quite good, but for some reason it was easier to figure out what was needed to get it working by reading the code.

Autocomplete / Typeahead

Tagged autocomplete, javascript, typeahead, bootstrap, jquery  Languages javascript, html

Javascript (jQuery):

function autocomplete(options) {
  var input = $(options.input);
  var resultsElem = $(options.results);
  var url = options.url;
  var cache = {};
  var result = null;
  var minQueryLength = options.minQueryLength;
  var debounce = null;
  var waitingFor = null; // Ajax responses might not arrive in order
  var show = function() {
    if(result == null) {
      return;
    }
    resultsElem.fadeIn(100);
  };
  var hide = function() {
    resultsElem.fadeOut(100);
  };
  // Hide when clicking outside autocomplete elements
  $('body').click(function(){
    hide();
  });
  // Stop propagation so that event does not reach the body handler 
  $('.autocomplete').click(function(e){
    e.stopPropagation();
  });
  var renderResults = function(query, results) {
    if (query !== waitingFor) {
      return;
    }
    resultsElem.html(results);
    if (results != undefined) {
      show();
    } else {
      hide();
    }
  };
  var search = function() {
    var query = input.val();
    console.log("Searching for '" + query + "'")
    waitingFor = query;
    if (query in cache) {
      console.log('Using cache')
      renderResults(query, cache[query]);
    } else {
      console.log('Not using cache')
      if (query.length > minQueryLength) {
        $.ajax({url: url, data: {query: query}, type: 'GET'}).then(function(data) {
          result = data;
          cache[query] = result;
          renderResults(query, result);
        });
      } else {
        renderResults(query, null);
      }
    }
  };
  var searchWithDebounce = function(e) {
    clearTimeout(debounce);
    debounce = setTimeout(search, 100);
  };
  input.on('blur', hide);
  input.on('focus', show);
  input.on('keyup', searchWithDebounce);
};

autocomplete({
  input: '#autoComplete',
  results: '#autocomplete-results',
  url: '/search',
  minQueryLength: 2
});

HTML (Bootstrap 4):

<form action="/search" class="form-inline ml-2 my-2 my-lg-0 ml-auto mr-auto" id="site-search" method="GET">
  <input aria-label="Search" autocomplete="off" class="autocomplete form-control mr-sm-2" id="autoComplete" name="query" placeholder="Search" tabindex="1" type="search">
  <div class="autocomplete shadow" id="autocomplete-results"></div>
</form>