Marco Polo
A jQuery autocomplete plugin for the discerning developer.
After spending years struggling with various autocomplete plugins, I became fed up with their bugginess, poor documentation, lack of updates, inflexibility, and antiquated coding patterns. Surely something as fundamental as autocomplete could — really, should — be done better. And now it has. Meet Marco Polo. For the discerning developer.
Developed by Justin Stayton while at Monk Development for the Ekklesia 360 CMS.
Features
- Cache and buffer. Marco Polo prevents unnecessary requests through its build-in results cache (shared by all instances) and key press buffer (only makes a request after the user has finished typing).
- Remembers selection. Once a result is selected, if that same result appears in the results again, it's automatically highlighted. This is very similar to how select inputs mark the currently selected item.
- Require selection. Marco Polo can be configured to require a selection be made from the results, ensuring that the text input is left empty when no selection is made.
- Overlabel support. Overlabel is the concept of placing a label element over a text input for a more compact display. Marco Polo offers built-in support for hiding and showing the label automatically, depending on the state of interaction with the plugin.
- Complete styling control. With straightforward markup that's explained in detail, you can easily style and modify all of the components to fit your needs and aesthetic.
- Callbacks for all major events. Add your own twist when a search is made, result is selected, error occurs, and more.
- Maintained. I developed this plugin for production use in the Ekklesia 360 CMS at Monk Development, so you can very much believe that it will remain bug-free and up-to-date. Any feature requests, bug reports, or feedback you submit will be responded to quickly as well.
- Documented. I believe that code is only as useful as its documentation. This manifests itself not only in clear and thorough developer documentation (below), but also verbose documentation within the code itself.
- WAI-ARIA support. Assistive technology users can fully understand and navigate Marco Polo.
Requirements
- jQuery >= 1.4.3
- jQuery UI Widget >= 1.8.21 (included in minified build)
- All modern browsers, including IE >= 6
Installation
Download
Bower
Bower is a package manager for the web. Once installed, Bower can install Marco Polo with a single command:
bower install jquery-marcopolo
Manually
Include
Include both jQuery and Marco Polo in your HTML:
<script src="jquery.min.js"></script>
<script src="jquery.marcopolo.min.js"></script>
In most cases, jquery.marcopolo.min.js
is the best file to include, as it
contains the required libraries and source code in a single minified package.
The build
directory contains a number of other files as well:
jquery.marcopolo.js
contains the required libraries and source code in a single unmifified package.build/parts
contains each individual library and source file in both minified and unminified varieties.
Getting Started
Let's say you want to create a user search field that redirects to the user's profile when a result is selected. To start, add a text input, if you haven't already:
<input type="text" name="userSearch" id="userSearch">
Then attach Marco Polo to the text input in your JavaScript:
$('#userSearch').marcoPolo({
url: '/users/search',
formatItem: function (data, $item) {
return data.first_name + ' ' + data.last_name;
},
onSelect: function (data, $item) {
window.location = data.profile_url;
}
});
When a search happens, a GET request is made to the url
with q
(the search value) added to the query string. (Additional data can be included
using the data
option.) Let's say a search is made for Butler. A GET
request is made to /users/search?q=Butler
. Your backend code must then use
the q
parameter to find and return the matching users in JSON format:
[
{
"first_name": "James",
"last_name": "Butler",
"profile_url": "/users/78749",
…
},
{
"first_name": "Win",
"last_name": "Butler",
"profile_url": "/users/41480",
…
},
…
]
Each JSON user object is passed to the formatItem
callback option for display
in the results list. And when a user is selected from the results list, their
JSON object is then passed to the onSelect
callback option to complete the
browser redirect.
You should now have enough understanding of Marco Polo to start configuring it for your specific needs. While this example demonstrates a number of fundamental concepts, the possibilities extend far beyond the straightforward search, click, redirect setup shown here. And when you're ready, consider reading through some of the more advanced guides:
Options
All options are optional, although url is usually specified unless the input field is in a form by itself (in which case the form's action attribute can be used).
-
cache boolean
Whether to cache query results. The cache is shared by all instances, which is a big advantage when many of the same field type appear on the same page. For example, a tags field that's repeated for every record on a page.
Default: true
-
compare boolean, string
Whether to compare the selected item against items displayed in the results list. The selected item is highlighted if a match is found, instead of the first item in the list (highlight option must be enabled). Set this option to true if the data is a string; otherwise, specify the data object attribute name to compare on.
Default: false
-
data object, string, function
Additional data to be sent in the request query string. (Note: The query string parameter that is set with the input value (param option) will overwrite the value in the data object if an attribute with the same name exists.)
Default: {}
When a function is used, it's called for every request, allowing the data to be dynamic. An object must be returned.
Parameters:
- q string Requested input value.
this: jQuery object Text input (no need to wrap like $(this)).
Return: object of additional data.
-
delay integer
The number of milliseconds to delay before firing a request after a change is made to the input value. This helps prevent an ajax request on every keystroke from overwhelming the server and slowing down the page.
Default: 250
-
hideOnSelect boolean
Whether to hide the results list when an item is selected. Interesting things can be done when this is set to false, such as hiding and showing certain items when other items are selected. The results list is still hidden when the input is blurred for any other reason.
Default: true
-
highlight boolean
Whether to automatically highlight an item when the results list is displayed. Usually it's the first item, but it could be the previously selected item if compare is specified.
Default: true
-
label selector, jQuery object, DOM element, null
Positioning a label over an input is a common design pattern (sometimes referred to as overlabel) that unfortunately doesn't work so well with all of the input focus/blur events that occur with autocomplete. With this option, however, the hiding/showing of the label is handled internally to provide a built-in solution to the problem. The label receives the class mp_label.
Default: null
-
minChars integer
The minimum number of characters required before a request is fired. See the formatMinChars callback to format the (optional) message displayed when this value is not reached.
Default: 1
-
param string
The name of the query string parameter that is set with the input value.
Default: q
-
required boolean
Whether to clear the input value when no selection is made from the results list. This happens when the input is blurred, usually by clicking or tabbing out of the field.
Default: false
-
selectable selector
The list items to make selectable. For example, say you add the class header to a number of list items (in the formatItem callback) that you want to act as non-selectable headers. They can be excluded with the selector :not(.header). Selectable items receive the class mp_selectable.
Default: *
-
selected object, null
Prime the input with a selected item. onSelect is called just as if the item were selected from the results list.
Default: null
-
submitOnEnter boolean
Whether to allow the browser's default behavior of submitting the form on ENTER.
Default: false
-
url string, null
The URL to GET request for the results, which must be an array of strings or JSON. If no URL is set, the parent form's action attribute value is used if one exists. q is added to the query string with the input value, along with any additional data.
Default: null
Callbacks
Formatting
-
formatData (data) function, null
Format the raw data that's returned from the ajax request. Useful for further filtering the data or returning the array of results that's embedded deeper in the object.
Default: null
Parameters:
- data array, object Data returned from the request.
this: jQuery object Text input (no need to wrap like $(this)).
Return: array of objects to use as the data.
-
formatError ($item, jqXHR, textStatus, errorThrown) function, null
Format the text that's displayed when the ajax request fails. The message is displayed in a list item with the class mp_error:
<li class="mp_error"> <em>Your search could not be completed at this time.</em> </li>
Setting this option to null or returning false suppresses the message from being displayed.
Default:
return '<em>Your search could not be completed at this time.</em>';
Parameters:
- $item jQuery object List item to display the message.
- jqXHR object or XMLHTTPRequest in jQuery 1.4.x.
- textStatus string Error status of the request.
- errorThrown string HTTP error status.
this: jQuery object Text input (no need to wrap like $(this)).
Return: string, DOM element, or jQuery object to use as the message.
-
formatItem (data, $item) function
Format the display of each item in the results list. By default, the title or name value of the data object is displayed. The returned value is added to a list item with the class mp_item:
<li class="mp_item">The Title of Something</li>
Default:
return data.title || data.name;
Parameters:
- data string, object Data returned from the request.
- $item jQuery object List item to display the result.
this: jQuery object Text input (no need to wrap like $(this)).
Return: string, DOM element, or jQuery object to use as the display.
-
formatMinChars (minChars, $item) function, null
Format the text that's displayed when the minimum number of characters (specified with the minChars option) hasn't been reached. The message is displayed in a list item with the class mp_min_chars:
<li class="mp_min_chars"> <em>Your search must be at least <strong>3</strong> characters.</em> </li>
Setting this option to null or returning false suppresses the message from being displayed. It is also not displayed when there is no input value.
Default:
return '<em>Your search must be at least <strong>' + minChars + '</strong>characters.</em>';
Parameters:
- minChars integer Minimum number of characters required.
- $item jQuery object List item to display the message.
this: jQuery object Text input (no need to wrap like $(this)).
Return: string, DOM element, or jQuery object to use as the message.
-
formatNoResults (q, $item) function, null
Format the text that's displayed when there are no results returned for the requested input value. The message is displayed in a list item with the class mp_no_results:
<li class="mp_no_results"> <em>No results for <strong>something</strong>.</em> </li>
Setting this option to null or returning false suppresses the message from being displayed.
Default:
return '<em>No results for <strong>' + q + '</strong>.</em>';
Parameters:
- q string Requested input value.
- $item jQuery object List item to display the message.
this: jQuery object Text input (no need to wrap like $(this)).
Return: string, DOM element, or jQuery object to use as the message.
Events
-
onBlur () function, null
Called when the user is finished interacting with the autocomplete interface, not just the text input, which loses and gains focus on a results list mouse click.
Default: null
Parameters: none
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopoloblur event:
$(selector).on('marcopoloblur', function (event) { … });
-
onChange (q) function, null
Called when the input value changes.
Default: null
Parameters:
- q string Changed input value.
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopolochange event:
$(selector).on('marcopolochange', function (event, q) { … });
-
onError ($item, jqXHR, textStatus, errorThrown) function, null
Called when the ajax request fails.
Default: null
Parameters:
- $item jQuery object List item to display the message.
- jqXHR object or XMLHTTPRequest in jQuery 1.4.x.
- textStatus string Error status of the request.
- errorThrown string HTTP error status.
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopoloerror event:
$(selector).on('marcopoloerror', function (event, $item, jqXHR, textStatus, errorThrown) { … });
-
onFocus () function, null
Called when the text input receives focus. This is different than the standard focus event on the text input, however, as this callback does not fire when a results list item is selected via mouse click, which causes the text input to blur and immediately gain focus again.
Default: null
Parameters: none
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopolofocus event:
$(selector).on('marcopolofocus', function (event) { … });
-
onMinChars (minChars, $item) function, null
Called when the minimum number of characters (specified with the minChars option) hasn't been reached by the end of the delay.
Default: null
Parameters:
- minChars integer Minimum number of characters required.
- $item jQuery object List item to display the message.
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopolominchars event:
$(selector).on('marcopolominchars', function (event, minChars, $item) { … });
-
onNoResults (q, $item) function, null
Called when there are no results returned for the request.
Default: null
Parameters:
- q string Requested input value.
- $item jQuery object List item to display the message.
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopolonoresults event:
$(selector).on('marcopolonoresults', function (event, q, $item) { … });
-
onRequestBefore () function, null
Called before the ajax request is made. Useful for showing a loading spinner if the request is going to take some time.
Default: null
Parameters: none
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopolorequestbefore event:
$(selector).on('marcopolorequestbefore', function (event) { … });
-
onRequestAfter (jqXHR, textStatus) function, null
Called after the ajax request completes (success or error). Useful for hiding a loading spinner that's shown in onRequestBefore.
Default: null
Parameters:
- jqXHR object or XMLHTTPRequest in jQuery 1.4.x.
- textStatus string Status of the request.
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopolorequestafter event:
$(selector).on('marcopolorequestafter', function (event, jqXHR, textStatus) { … });
-
onResults (data) function, null
Called when there are results to be displayed.
Default: null
Parameters:
- data array Data returned from the request.
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopoloresults event:
$(selector).on('marcopoloresults', function (event, data) { … });
-
onSelect (data, $item, initial) function, null
Called when an item is selected from the results list or an initial value (see Setting an Initial Value). By default, the title or name value of the data object is used to populate the input value.
Default:
this.val(data.title || data.name);
Parameters:
- data string, object Data returned from the request.
- $item jQuery object, null Selected results list item. null if selected option used.
- initial boolean Whether this is an initial value.
this: jQuery object Text input (no need to wrap like $(this)).
Event: You can also bind to the marcopoloselect event:
$(selector).on('marcopoloselect', function (event, data, $item, initial) { … });
Methods
-
change
Programmatically change the input value without triggering a search request (use the search method for that). If the value is different than the current input value, the onChange callback is fired.
Example:
$('#userSearch').marcoPolo('change', 'Wilson');
Parameters:
- q string New input value.
-
destroy
Remove the autocomplete functionality and return the selected input fields to their original state.
Example:
$('#userSearch').marcoPolo('destroy');
-
list
Get the results list element.
Example:
$('#userSearch').marcoPolo('list');
-
option
Get or set one or more options.
Example:
Get a specific option:
$('#userSearch').marcoPolo('option', 'url');
Get the entire options object:
$('#userSearch').marcoPolo('option');
Set a specific option:
$('#userSearch').marcoPolo('option', 'url', '/new/url');
Set multiple options:
$('#userSearch').marcoPolo('option', { url: '/new/url', onSelect: function (data, $item) { … } });
Parameters:
- nameOrValues string, object Optional options to get or set.
- value mixed Optional option value to set.
-
search
Programmatically trigger a search request using the existing input value or a new one. The input receives focus to allow for keyboard navigation.
Example:
Trigger a search on the existing input value:
$('#userSearch').marcoPolo('search');
Trigger a search on a new value:
$('#userSearch').marcoPolo('search', 'Wilson');
Parameters:
- q string Optional new input value to search on.
-
select
Set the currently selected data, just as if the user clicked or keyboard selected an item from the results list. The onSelect callback is fired.
Example:
$('#userSearch').marcoPolo('select', { first_name: 'Lindsay', … });
Parameters:
- data string, object Data of the selected item.
-
selected
Get the currently selected data (string, object, or null if not set).
Example:
$('#userSearch').marcoPolo('selected');
Feedback
Please open an issue to request a feature or submit a bug report. Or even if you just want to provide some feedback, I'd love to hear. I'm also available on Twitter as @jstayton.
Contributing
- Fork it.
- Create your feature branch (
git checkout -b my-new-feature
). - Commit your changes (
git commit -am 'Added some feature'
). - Push to the branch (
git push origin my-new-feature
). - Create a new Pull Request.