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.
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.
The source for this search is the top 200 baby names of 2012.
Learn more about this demo here.
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.
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 renderedselected-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
elementinput
- 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 expectedpicker
- container for the picker list. This will be the target of positioning and show/hide
methods as a search is performedpicker-list
- the containing element where search results should be renderedpicker-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
elementThe 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.
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
function ( term, callback ) { ... }
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:
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 ) { ... }
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:
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
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.
added
: once the item is added, this event is triggered with the data and element.
removing
: prior to removing the item, this event is triggered to allow canceling
the operation by returning false.
removed
: upon removing the item, this event is triggered
searching
: triggered before searching but immediately after displaying the picker
searched
: triggered after searching has completed and results have been returned by the source
itemaction
: triggered when an element with data-action
is clicked in the item list
itemselect
: triggered when an item is clicked in the item list (no data-action
defined)
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:
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 Please open a ticket via the issue log on the GitHub project page:
https://github.com/bseth99/jqueryui-multisearch/issues
Copyright (c) 2013 Ben Olson
Licensed under the MIT License