question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

autocomplete: allow function for asynchronous completions

See original GitHub issue

It would be nice if a function could be specified to provide completions asynchronously. This could be useful for datasets that cannot be located completely on the client and must be fetched partially from the server, as in the following example:

$("input").autocomplete({
    fetch: function (val, callback) {
        fetch("/api/complete?query=" + encodeURIComponent(val))
            .then(function (response) {
               response.json().then(function (json) {
                   callback(null, json);
               });
            });
    }
});

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:3
  • Comments:7

github_iconTop GitHub Comments

2reactions
diegonccommented, Jul 30, 2016

By the way, this is how you would use the approach above for querying GitHub:

“Data model” definition:

function GitHubReposModel (user) {
  this.user = user;
}
GitHubReposModel.prototype.complete = function (input, callback) {
  $.ajax({
    url: "https://api.github.com/users/"+encodeURIComponent(this.user)+"/repos",
    error: function (xhr, status, error) {
      callback(error);
    },
    success: function (data) {
      /* GitHub endpoint doesn't allow filtering server-side */
      var lci = input.toLowerCase();
      var filtered = data.filter(function (item) {
        return item.name && item.name.toLowerCase().indexOf(lci) !== -1;
      });
      callback(null, filtered);
    }
  });
};
GitHubReposModel.prototype.render = function (item, input) {
  var highlight = '<span class="highlight">$&</span>';
  var text = item.name.replace(new RegExp(input, 'i'), highlight);
  var li = $('<li></li>');
  var span = $('<span></span>');
  if (item.language) {
    var encodedLanguage = encodeURIComponent(item.language);
    var src = "http://placehold.it/40x40/?text=" + encodedLanguage;
    var img = $('<img src="' + src + '" class="right circle"></img>');
    li.append(img);
  }
  span.html(text);
  li.append(span);
  li.data("value", item.name);
  return li;
};
GitHubReposModel.prototype.extract = function (li) {
  return $(li).data("value");
};

Initialization:

$('input.autocomplete-xhr').autocomplete({
  model: new GitHubReposModel("dogfalo")
});

Markup:

<p>Autocomplete with XHR</p>
<div class="row">
  <div class="col s12">
    <div class="row">
      <div class="input-field col s12">
        <i class="material-icons prefix">textsms</i>
        <input type="text" id="autocomplete-xhr" class="autocomplete-xhr">
        <label for="autocomplete-xhr">Autocomplete XHR</label>
      </div>
    </div>
  </div>
</div>
1reaction
diegonccommented, Jul 30, 2016

What I was thinking of is a client provided hook around the folowing point in js/forms.js#L319…327:

          // Perform search
          $input.on('keyup', function (e) {
            // Capture Enter
            if (e.which === 13) {
              $autocomplete.find('li').first().click();
              return;
            }

            var val = $input.val().toLowerCase();

such that the hook gets called with val and is in charge of generating the dataset for that text, maybe asynchronously, which then gets rendered into li tags.

Also, I believe the code could be improved by extracting the different concerns to configurable functions, namely completion, rendering and value extraction, making the component agnostic of data format and capable of supporting different layouts in the suggestions panel.

Like in the following code (it’s just a draft, I need to test it yet):

/* Sample data model which accepts data in the format used
 * by the current implementation of Materialize's autocomple.
 */
function ClientDataModel (data) {
  var a = [];
  for(var key in data) {
    if (data.hasOwnProperty(key)) {
      a.push({text: key, img: data[key]});
    }
  }
  this.data = a;
}

/* fn: complete
 *
 * Given an `input` text, produce a list of suggestions and
 * pass it back to the caller provided `callback`.
 *
 * The returned value is an array but the format of each item
 * it holds remains unspecified and the caller should only
 * handle them through other methods of this class.
 */
ClientDataModel.prototype.complete = function (input, callback) {
  var lowerCasedInput = input.toLowerCase();
  var filtered = this.data.filter(function (item) {
    return item.text.toLowerCase().indexOf(lowerCasedInput) !== -1    
  });
  callback(null, filtered);
};

/* fn: render
 *
 * Given one data `ìtem` (that has been produced by `complete`) and
 * an `ìnput`, render the item to a `li` element where the input is
 * highlighted.
 */
ClientDataModel.prototype.render = function (item, input) {
  var highlight = '<span class="highlight">$&</span>';
  var text = item.text.replace(new RegExp(input, 'i'), highlight);
  var li = $('<li></li>');
  var span = $('<span></span>');
  if (item.img) {
    var img = $('<img src="' + item.img + '" class="right circle"></img>');
    li.append(img);
  }
  span.html(text);
  li.append(span);
  return li;
};

/* fn: extract
 *
 * Given a `li` element rendered by `render`, return the value the input
 * field should be set to.
 */
ClientDataModel.prototype.extract = function (li) {
  return $(li).text().trim();
};

$.fn.autocomplete = function (options) {
  if (options.data) options.model = new ClientDataModel(options.data);

  if (!options.model) {
    console.error("Autocomplete does not have a data model.");
    return;
  }

  return this.each(function() {
    var $input = $(this),
        $inputDiv = $input.closest('.input-field'); // Div to append on

    // Create autocomplete element
    var $autocomplete = $('<ul class="autocomplete-content dropdown-content"></ul>');

    // Append autocomplete element
    if ($inputDiv.length) {
      $inputDiv.append($autocomplete); // Set ul in body
    } else {
      $input.after($autocomplete);
    }

    // Perform search
    $input.on('keyup', function (e) {
      // Capture Enter
      if (e.which === 13) {
        $autocomplete.find('li').first().click();
        return false;
      }

      var val = $input.val();

      // Check if the input isn't empty
      if (val !== '') {
        options.model.complete(val, function (err, suggestions) {
          if (err) return console.error(err); //TODO: handle errors
          $autocomplete.empty();
          suggestions.forEach(function (item) {
            var li = options.model.render(item, val);
            $autocomplete.append(li);
          });
        });
      } else {
        $autocomplete.empty();
      }
    });

    // Set input value
    $autocomplete.on('click', 'li', function () {
      $input.val(options.model.extract(this));
      $autocomplete.empty();
    });
  });
};

It may look quite complex, but there are a few improvements here:

  1. The data structure holding suggestions is an array; thus avoiding ordering issues *
  2. The autocomplete client may specify a custom “data model” allowing for more complex data than a pair of text and image and the possibility to display that data using a custom layout.
  3. Because the rendering of suggestions is done in a callback asynchronous completions are supported.

The last one is what this issue is about and it’s very useful for datasets that won’t fit in the client unless pre-filtered server-side.

It may make sense not to expose the data model to the client; it’s a material design library after all. But having it makes it easy to support the different designsø.

Read more comments on GitHub >

github_iconTop Results From Across the Web

AutoComplete - Node-RED
This allows the function to do asynchronous work to generate the completions. The returns list of completions must be an array of objects...
Read more >
Example: Autocompletion - CodeMirror
Providing Completions​​ Sources may run asynchronously by returning a promise. The easiest way to connect a completion source to an editor is to...
Read more >
Completions - Sublime Text
Asynchronous completions can be provided by on_query_completions() returning a sublime.CompletionList object. Completion details, including kind metadata, are ...
Read more >
Type less, code more with IntelliCode completions
In Visual Studio 2022 Preview 1 you can automatically complete code, up to a whole line at a time! Check out the video...
Read more >
c# - Autocomplete Method Brackets - Stack Overflow
When doing autocomplete, instead of just hitting ENTER, you can instead press the key combination "Shift + (" and it will autocomplete and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found