jQuery UI MultiSearch

A jQuery UI widget that enables building a list of multiple items using an autocomplete entry box without imposing specific HTML structure or CSS style rules.

 

Download Latest Version

Overview

The goal of this project is to create a widget that is as unopinionated about the structure and styling of the content as possible. There are many variations of building a list of items by looking each item up using a type ahead field but few will ever look the same. However, the basic underlying functionality is very similar. This widget emphasizes building features that encapsulate that control logic and provide many hooks along the way to customize the behavior.

Quick Demo

The source for this search is the top 200 baby names of 2012.

Learn more about this demo here.

Features

  • Type ahead partial searching with suggested results against either a remote or local data source
  • Built-in caching and throttling logic to help alleviate load on a remote data source
  • Result records can have any number of attributes which are passed through to template functions and triggered event handlers
  • Multiple keys and search fields can be defined to enable duplicate detection, lookups, and hit highlighting
  • Keyboard interaction to navigate suggestion list and selected items
  • Many event hooks and callbacks to customize behavior and control layout and styling
  • API methods to get/set the list of selected items and add/remove individual items

Quick Start

MultiSearch has dependencies on jQuery, jQuery UI, and Underscore (if you prefer LoDash, it can be used instead). The widget makes extensive use of several of the data transversal and manipulation function in the Underscore library which helps reduce the code size and maintenance (no reason to reinvent that wheel). You can download these or just use the CDN versions:

<script src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.1.0/lodash.underscore.min.js"></script>
         

Next, include the MultiSearch file and you're ready to go:

<script src="jqueryui-multisearch.min.js"></script>
         

Here's some example mark up that defines the container of the widget and the four required points in that structure where the widget should attach certain functionality:


<div id="myMultiSearch">
   <div data-role="selected-list">
      <input data-role="input" type="text" placeholder="Search..."/>
   </div>

   <div data-role="picker" class="picker">
      <ul class="list-group" data-role="picker-list">
      </ul>
   </div>
</div>

These roles can be placed anywhere inside the widget's containing element (although, you may need to change a few options on the widget so it makes the right assuptions about where a given item is located).

Once that is defined, you can target the containing element and initialize it with the multisearch widget plugin. The only required option is the source data which can simply be an array of objects. Everything else will default to provide basic functionality.


$(function() {

   $("#myMultiSearch").multisearch({
      source: [
         {name: 'One'},
         {name: 'Two'},
         {name: 'Three'},
         {name: 'Four'}
      ],
   });

});

If you run the above code with no styling, it will work fine but there won't be any highlighting and the selected items will run together. With the HTML defined above, these styles will make it look a little better:


   ul {
      list-style: none;
      margin: 0;
      padding: 0;
   }

   li {
      margin: 0;
      padding: 0;
   }

   a {
      color: black;
      margin: 3px;
   }

   a:hover, a.hover, li.hover a {
      color: green;
   }

   .picker {
      border: 1px solid silver;
      padding: 10px;
   }

The example is straight from the basic demo below. Take a look at it for the full source and to see it working (with and without the styling). I also setup a jsFiddle with the basic demo that you can use to tinker with some of the options and styling.

Demos

Reference

The mark up needs to define several roles via a data-role attribute. The widget will use those binding points to attach the required control logic. The list of roles include:

  • selected-list - the containing element where selected items should be rendered
  • selected-item - auto-assigned by the widget as new items are generated with the factory template function defined by formatSelectedItem. These elements will be direct children of the selected-list element
  • input - this should be the input box where the user will type searches. Depending on where it is positioned relative to selected-list, you may need to adjust the pickerPosition option so keyboard interation works as expected
  • picker - container for the picker list. This will be the target of positioning and show/hide methods as a search is performed
  • picker-list - the containing element where search results should be rendered
  • picker-item - auto-assigned by the widget as new items are generated with the factory template function defined by formatPickerItem. These elements will be direct children of the picker-list element

The keyboard can be used to navigate both the picker items when a search is being performed and the list of selected items when no text is in the input box. The navigation will trigger the same hover/active events that would occur if using the mouse.

Options

This is the list of available options when initializing the widget. The only required option is the source the widget will use for searching and the item content.

source: The data source to search. Can be a string, function, or array of objects

  • A string should be a valid remote datasource
  • A function can implement a data search and should call the passed in callback with the results:

        function ( term, callback ) { ... }

  • An array repesents a local dataset and all searches will be done locally on the contents

Default: null


ajaxOptions:Hash of options that are used in the $.ajax call on a remote resource. Only used when source is a string representing the path of the resource. Currently accepts overrides for dataType and method options. Also adds a custom options:

  • searchTerm: the parameter name that will contain the search term ( ie path/to/resource?term=abc )

Default: {
   searchTerm: 'term',
   dataType: 'json',
   method: 'GET'
}


keyAttrs: An array of fields in the result object which represent the unique key for the object. Used to detect duplicate items. If not set, will default to ['id']

Default: [ 'id' ]


searchAttrs: An array of fields in the result object to search for the entered criteria.

Default: [ 'name' ]


formatPickerItem: A function that returns an HTML string repesenting the item to add to the suggestion picker as a search is entered into the input.. The hash returned by the remote server or local cache is passed to the function. This can be a Underscore template() function.

Default: function( data ) { return '<li><a href="#">'+data.name+'</a></li>'; }


formatSelectedItem: A function that returns an HTML string repesenting the item to add to the selected items list. Called each time a search term is selected from the suggestion picker or, when not found items are allowed, a new entry is completed. The hash from the result set is passed to the function. This can be a Underscore template() function.

Default: function( data ) { return '<a href="#">'+data.name+'</a>'; }


buildNewItem: When adding items that are not found in the suggestion picker, this function is called to define the object that is expected in the formatSelectedItem() template. Generally, you'll leave the keyAttrs attributes null and set the primary display field with the text from the input box which is passed to the function.

Default: function( text ) { return { id: null, name: text }; }


minSearchChars: How many characters need to be typed to trigger a search

Default: 2


searchThrottle: How quickly can successive searches be triggered. The value is in milliseconds and uses Underscore's throttle() function to control triggering calls to the remote resource. This does not affect local cache searching.

Default: 200


maxShowOptions: How many results to show in the picker. Even if 200 are returned by the server, you can control how many are actually displayed in the suggestion picker. Set it to zero to show everything. Ensure you enable some kind of scrolling on the element you define as the picker/picker-list role if you allow longer lists of items.

Default: 5


minLocalCache: When to start refining a search against the local cache. Each remote search is saved by term if the remote result set has less than minLocalCache items in it, each subsequent character typed will use that result as a basis for searching. This can reduce the number of hits on the remote resource and can be fine-tuned to match your needs. Set it to zero to disable local refinements against the cache. If you do use it, make sure any limits on the server side are set high enough to ensure that a search term that refines the remote search below this threshold will contain all possible items that could be found if the subsequent searches were run against the server.

Default: 50


preventNotFound: Can items not found in the suggestion picker be added to the seleced item list. If allowed, buildNewItem() will be called to allow setting defaults on the object that represents the search data to be prefilled before calling formatSelectedItem(). The adding and added events will have the notfound flag set to true when an item will be added that in not in the picker.

Default: false


preventDuplicates: Using the keyAttrs, should duplicates be prevented. A duplicate event is triggered if one is found allowing custom UI logic to be defined externally. Otherwise, nothing will happen. The suggestion picker will remain open and it will appear that the widget is not responding. Items that are not found in the picker but are added to the selected item list will not be considered a duplicate unless you provide custom logic during adding that would assign a key.

Default: true


useAutoWidth: Automatically resize the input box to match the text. Helpful for inline/floating elements so the input only wraps as needed. Disable if using block elements that you want to fill the parent space.

Default: true


pickerPosition: Use jQueryUI.position plugin to position the picker box relative to the input control. The default is the basic drop-down menu box under the input entry box. Depending on the structure, you may want to adjust this setting.

Default: { my: 'left top', at: 'left bottom' }


inputPosition: Where is the input relative to the item list. Determines how the keyboard navigation moves through the items and where new items are added. Valid values are "start" and "end" defaults to "end". Depending on where you position the input box, adjust this setting so the UI interactions make sense.

Default: end


localMatcher:

For each field defined in searchAttrs, search for the input text using the function below. This is used for both local cache searches and hit highlighting. It should match with the search method from the remote. The default is to look for the search string anywhere in the target field. If you want to match only from the leading edge of the field, you'll need to override this function. If you override it, the function must accept two parameters and return true (needle found in haystack) or false (not found):

    function( needle, haystack ) { ... }

API

Several methods are available to interact with the selected items list via code. These methods all suppress any events that would normally be triggered by user interaction.

value: Getter/Setter for list of selected items. Use it to retreive the list of items entered by the user or seed the list with existing items (from a database, etc).

Getting the value returns a shallow clone of the objects in the item list. If you're using nested objects from a shared dataset, be aware you may be referencing them in the returned set.

Setting the value will destory the current selections. If you want more control, use add/remove to selectively update the list.


add: Add an item to the selection list. Optional second arguement can be used to specify the position to insert the item. If the item already exists, it will be merged and trigger rendering the content of the item again such that updated data can be applied to the list.


remove: Remove one or all items form the selected list:

  • No arguements will remove everything
  • Pass an Integer representing the ordinal index of the item to remove
  • Pass an Object containing the keys of the item to remove

Events

Uses the standard jQuery UI interface for triggering events. You can either define a callback in the options hash or listen to the event by binding to multisearch + event name ie $.on( 'multisearchadding', ... ).

Handler function will receive an event and ui argument. The ui object is defined below with each event that is triggered.

duplicate: trigger when preventDuplicates is true and a selected item from the picker matches an item already in the item list based on keyAttrs

  • ui: {
    • existing: Object representing the duplicate already present in the item list
    • adding: Object representing the item that is attempting to be added
  • }

adding: triggered before actually adding the item to the item list. Return false to prevent the the action. You can modify data to affect what is passed to the template function and retained in the item list.

  • ui: {
    • data: Object containing the selected item
    • notfound: Flag indicating whether the item was found in the picker list
  • }

added: once the item is added, this event is triggered with the data and element.

  • ui: {
    • data: Object containing the selected item
    • element: jQuery object of the newly added element representing the data in the UI
  • }

removing: prior to removing the item, this event is triggered to allow canceling the operation by returning false.

  • ui: {
    • data: Object containing the selected item
  • }

removed: upon removing the item, this event is triggered

  • ui: {
    • data: Object containing the selected item
  • }

searching: triggered before searching but immediately after displaying the picker

  • ui: {
    • term: String that will be used in the search
    • picker: jQuery object representing the main picker container element
  • }

searched: triggered after searching has completed and results have been returned by the source

  • ui: {
    • term: String that will be used in the search
    • picker: jQuery object representing the main picker container element
    • list: Array of objects returned by the search
  • }

itemaction: triggered when an element with data-action is clicked in the item list

  • ui: {
    • data: Object containing the selected item
    • element: jQuery object of the element representing the data in the UI
  • }

itemselect: triggered when an item is clicked in the item list (no data-action defined)

  • ui: {
    • data: Object containing the selected item
    • element: jQuery object of the element representing the data in the UI
  • }

Hover/Active events are triggered either when the user uses the mouse (hover/click) or uses the keyboard to navigate (arrows, space bar). A handler can return false to prevent the default action which simply changes classes on the elements. Each event provides the following ui object:

  • ui: {
    • target: A jQuery object representing the item receiving the event
    • siblings: jQuery object of the siblings of the target filtering out any elements that do not have the data-role defined. Generally, you need to remove classes from the siblings when adding them to the target.
  • }

Here is the list of interaction events:

  • selectedactive: When a selected item is clicked or selected with the space bar
  • selectedhoverin: When mousing into an item or using the arrows to navigate
  • selectedhoverout: When leaving a selected item
  • pickerhoverin: When mouseover or arrows to a suggested item in the picker list
  • pickerhoverout: When leaving the item

Ideas, Issues, and Requests

Please open a ticket via the issue log on the GitHub project page:

https://github.com/bseth99/jqueryui-multisearch/issues

License

Copyright (c) 2013 Ben Olson

Licensed under the MIT License