2012-08-15 18:26:11 +00:00
|
|
|
/*
|
|
|
|
* jQuery multiSuggeset Plugin v.01
|
|
|
|
*
|
|
|
|
* Copyright 2012, Rob Moen
|
|
|
|
* http://sane.ly
|
|
|
|
* This document is licensed as free software under the terms of the
|
|
|
|
* MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
*
|
|
|
|
// Input element.
|
|
|
|
var $input = $( '#exampleInput' );
|
|
|
|
// Multi Suggest configuration.
|
|
|
|
var options = {
|
|
|
|
'parent': $input.parent(),
|
|
|
|
'prefix': 'example-multi',
|
|
|
|
|
|
|
|
// Build suggestion groups in order.
|
2012-08-27 20:04:04 +00:00
|
|
|
'suggestions': function ( params ) {
|
2012-08-15 18:26:11 +00:00
|
|
|
// Generic params object.
|
|
|
|
var example = params.example,
|
|
|
|
example2 = params.example2,
|
|
|
|
query = params.query;
|
|
|
|
groups = {
|
|
|
|
// Set 1
|
2012-08-27 20:04:04 +00:00
|
|
|
'query': {
|
|
|
|
'label': 'Query',
|
|
|
|
'items': [query],
|
|
|
|
'itemClass': 'query-class'
|
2012-08-15 18:26:11 +00:00
|
|
|
},
|
|
|
|
// Set 2
|
2012-08-27 20:04:04 +00:00
|
|
|
'exampleGroup': {
|
|
|
|
'label': 'Example 1',
|
|
|
|
'items': example,
|
|
|
|
'itemClass': 'example-class'
|
2012-08-15 18:26:11 +00:00
|
|
|
},
|
|
|
|
// Set 3
|
2012-08-27 20:04:04 +00:00
|
|
|
'exampleGroup2': {
|
|
|
|
'label': 'Example 2',
|
|
|
|
'items': example2,
|
|
|
|
'itemClass': 'example-class'
|
2012-08-15 18:26:11 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
// Return the groups object.
|
|
|
|
return groups;
|
|
|
|
},
|
|
|
|
// Called on succesfull input.
|
2012-08-27 20:04:04 +00:00
|
|
|
'input': function ( callback ) {
|
2012-08-15 18:26:11 +00:00
|
|
|
var query = $input.val();
|
|
|
|
// Example params object.
|
|
|
|
var params = {
|
2012-08-27 20:04:04 +00:00
|
|
|
'query': query,
|
|
|
|
'example': ['example item 1', 'example item 2', 'example item 3', 'example item 4'],
|
|
|
|
'example2': ['example item 5', 'example item 6']
|
2012-08-15 18:26:11 +00:00
|
|
|
};
|
|
|
|
// Build with params.
|
|
|
|
callback( params );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Setup
|
|
|
|
$input.multiSuggest( options );
|
|
|
|
|
|
|
|
*/
|
|
|
|
( function ( $ ) {
|
|
|
|
$.fn.multiSuggest = function ( options ) {
|
2012-08-27 20:04:04 +00:00
|
|
|
return this.each( function () {
|
2012-08-15 18:26:11 +00:00
|
|
|
// Private members.
|
|
|
|
var inputTimer = null,
|
|
|
|
visible = false,
|
|
|
|
focused = false,
|
|
|
|
$input = $( this ),
|
2012-08-21 17:40:56 +00:00
|
|
|
currentInput = '',
|
2012-08-24 17:46:06 +00:00
|
|
|
$multiSuggest;
|
2012-08-15 18:26:11 +00:00
|
|
|
|
|
|
|
// Merge options with default configuration.
|
|
|
|
$.extend( {
|
2012-08-27 20:04:04 +00:00
|
|
|
'doc': document,
|
2012-08-31 20:46:03 +00:00
|
|
|
'prefix': 'multi',
|
|
|
|
'cssEllipsis': true
|
2012-08-15 18:26:11 +00:00
|
|
|
}, options );
|
|
|
|
|
|
|
|
// DOM Setup.
|
2012-08-28 22:06:34 +00:00
|
|
|
$multiSuggest = $( '<div>', options.doc )
|
|
|
|
.addClass( options.prefix + '-suggest-select' )
|
|
|
|
.hide();
|
|
|
|
$( options.parent ).append( $multiSuggest );
|
2012-08-15 18:26:11 +00:00
|
|
|
|
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
// Hides & Show MultiSuggest.
|
|
|
|
function toggle() {
|
|
|
|
if ( visible ) {
|
|
|
|
close();
|
|
|
|
} else {
|
|
|
|
open();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Call configured input method and supply the private build method as callback.
|
|
|
|
function onInput() {
|
2012-08-21 17:40:56 +00:00
|
|
|
// Throttle
|
|
|
|
clearTimeout( inputTimer );
|
2012-08-27 20:04:04 +00:00
|
|
|
inputTimer = setTimeout( function () {
|
2012-08-24 17:46:06 +00:00
|
|
|
var txt = $input.val();
|
2012-08-21 17:40:56 +00:00
|
|
|
if ( txt !== '' ) {
|
2012-08-23 22:43:06 +00:00
|
|
|
// Be sure that input has changed.
|
2012-08-24 17:46:06 +00:00
|
|
|
if (
|
|
|
|
txt !== currentInput &&
|
|
|
|
typeof options.input === 'function'
|
|
|
|
) {
|
2012-08-27 20:04:04 +00:00
|
|
|
options.input.call( $input, function ( params, callback ) {
|
2012-08-24 17:46:06 +00:00
|
|
|
build( params );
|
|
|
|
} );
|
2012-08-21 17:40:56 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// No Text, close.
|
|
|
|
if ( visible ) {
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
}
|
2012-08-23 22:43:06 +00:00
|
|
|
// Set current input.
|
|
|
|
currentInput = txt;
|
|
|
|
|
2012-08-21 17:40:56 +00:00
|
|
|
}, 250 );
|
2012-08-15 18:26:11 +00:00
|
|
|
}
|
|
|
|
// Opens the MultiSuggest dropdown.
|
|
|
|
function open() {
|
|
|
|
if ( !visible ) {
|
|
|
|
// Call input method if cached value is stale
|
|
|
|
if (
|
|
|
|
$input.val() !== '' &&
|
2012-08-24 17:46:06 +00:00
|
|
|
$input.val() !== currentInput
|
2012-08-15 18:26:11 +00:00
|
|
|
) {
|
|
|
|
onInput();
|
|
|
|
} else {
|
|
|
|
// Show if there are suggestions.
|
|
|
|
if ( $multiSuggest.children().length > 0 ) {
|
|
|
|
visible = true;
|
|
|
|
$multiSuggest.show();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Closes the dropdown.
|
|
|
|
function close() {
|
|
|
|
if ( visible ) {
|
2012-08-27 20:04:04 +00:00
|
|
|
setTimeout( function () {
|
2012-08-15 18:26:11 +00:00
|
|
|
visible = false;
|
|
|
|
$multiSuggest.hide();
|
|
|
|
}, 100 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// When an item is selected in the dropdown.
|
|
|
|
function select( text ) {
|
|
|
|
// Cache input.
|
2012-08-21 17:40:56 +00:00
|
|
|
currentInput = text;
|
2012-08-15 18:26:11 +00:00
|
|
|
$input.val( text );
|
|
|
|
close();
|
2012-09-10 21:31:18 +00:00
|
|
|
if ( typeof options.select === 'function' ) {
|
|
|
|
options.select.call( this );
|
|
|
|
}
|
2012-08-15 18:26:11 +00:00
|
|
|
}
|
|
|
|
// When an item is "clicked".
|
|
|
|
// Use of mousedown to prevent blur.
|
|
|
|
function onItemMousedown ( e ) {
|
|
|
|
e.preventDefault();
|
|
|
|
$multiSuggest
|
|
|
|
.find( '.' + options.prefix + '-suggest-item' )
|
|
|
|
.removeClass( 'selected' );
|
|
|
|
$( this ).addClass( 'selected' );
|
2012-08-31 20:46:03 +00:00
|
|
|
select.call( this, $( this ).data( 'text' ) );
|
2012-08-15 18:26:11 +00:00
|
|
|
}
|
|
|
|
// Adds a group to the dropdown.
|
|
|
|
function addGroup( name, group ) {
|
|
|
|
var $groupWrap,
|
|
|
|
$group,
|
|
|
|
$item,
|
|
|
|
i;
|
|
|
|
// Add a container with a label for this group.
|
|
|
|
$group = $( '<div>', options.doc )
|
2012-08-27 21:34:08 +00:00
|
|
|
.addClass( options.prefix + '-suggest-container' )
|
2012-08-15 18:26:11 +00:00
|
|
|
.append(
|
|
|
|
$( '<div>', options.doc )
|
2012-08-27 21:34:08 +00:00
|
|
|
.addClass( options.prefix + '-suggest-label' )
|
2012-08-15 18:26:11 +00:00
|
|
|
.text( group.label )
|
2012-08-28 22:06:34 +00:00
|
|
|
)
|
|
|
|
.append(
|
|
|
|
$( '<div>', options.doc ).addClass( options.prefix + '-suggest-wrap' )
|
|
|
|
)
|
2012-08-15 18:26:11 +00:00
|
|
|
// Add a clear break.
|
2012-08-28 22:06:34 +00:00
|
|
|
.append( $( '<div style="clear: both;">', options.doc ) );
|
2012-08-17 22:13:19 +00:00
|
|
|
// Add group
|
|
|
|
$multiSuggest.append( $group );
|
|
|
|
|
2012-08-15 18:26:11 +00:00
|
|
|
// Find the group wrapper element.
|
|
|
|
$groupWrap = $group.find( '.' + options.prefix + '-suggest-wrap' );
|
|
|
|
// If no items, add a dummy element to take up space.
|
|
|
|
if ( group.items.length === 0 ) {
|
|
|
|
$groupWrap.append(
|
2012-08-28 22:06:34 +00:00
|
|
|
$( '<div> </div>', options.doc )
|
2012-08-27 21:34:08 +00:00
|
|
|
.addClass( options.prefix + '-suggest-dummy-item' )
|
2012-08-15 18:26:11 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
// Add each item.
|
|
|
|
for( i = 0; i < group.items.length; i++ ) {
|
|
|
|
$item = $( '<div>', options.doc )
|
2012-08-27 21:34:08 +00:00
|
|
|
.addClass( options.prefix + '-suggest-item' )
|
2012-08-31 20:46:03 +00:00
|
|
|
.data( 'text', group.items[i] )
|
2012-08-28 22:06:34 +00:00
|
|
|
.on( 'mousedown', onItemMousedown );
|
2012-08-27 20:04:04 +00:00
|
|
|
if ( 'itemClass' in group ) {
|
|
|
|
$item.addClass( group.itemClass );
|
|
|
|
}
|
2012-08-31 20:46:03 +00:00
|
|
|
$groupWrap.append( $item );
|
|
|
|
// Wrap in span
|
|
|
|
$item.append( $( '<span>' )
|
|
|
|
.css( 'whiteSpace', 'nowrap' )
|
|
|
|
.text( group.items[i] )
|
|
|
|
);
|
2012-08-15 18:26:11 +00:00
|
|
|
// Select this item by default
|
2012-08-17 22:13:19 +00:00
|
|
|
if ( group.items[i].toLowerCase() === $input.val().toLowerCase() ) {
|
2012-08-15 18:26:11 +00:00
|
|
|
$item.addClass( 'selected' );
|
|
|
|
}
|
2012-08-31 20:46:03 +00:00
|
|
|
// CSS Ellipsis
|
|
|
|
if ( options.cssEllipsis ) {
|
|
|
|
$item.css( {
|
|
|
|
'white-space': 'nowrap',
|
|
|
|
'overflow': 'hidden',
|
|
|
|
'-o-text-overflow': 'ellipsis',
|
|
|
|
'text-overflow': 'ellipsis'
|
|
|
|
} );
|
|
|
|
}
|
2012-08-15 18:26:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Build the dropdown.
|
|
|
|
// Fired as callback in configured input event.
|
|
|
|
function build( params ) {
|
|
|
|
var suggestions = options.suggestions( params ),
|
|
|
|
group;
|
|
|
|
// Setup groups
|
|
|
|
$multiSuggest.empty();
|
|
|
|
if ( suggestions !== undefined ) {
|
|
|
|
for ( group in suggestions ) {
|
|
|
|
if ( $.isPlainObject( suggestions[group] ) ) {
|
|
|
|
addGroup( group, suggestions[group] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Open dropdown.
|
|
|
|
open();
|
2012-08-31 20:46:03 +00:00
|
|
|
// Run update method supplied in configuration.
|
|
|
|
if ( typeof options.update === 'function' ) {
|
|
|
|
options.update();
|
|
|
|
}
|
2012-08-15 18:26:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Bind target input events
|
|
|
|
$input.on( {
|
|
|
|
// Handle any change to the input.
|
2012-08-27 20:04:04 +00:00
|
|
|
'keydown cut paste': function ( e ) {
|
2012-08-15 18:26:11 +00:00
|
|
|
var $item,
|
|
|
|
$items = $multiSuggest
|
|
|
|
.find( '.' + options.prefix + '-suggest-item' ),
|
|
|
|
selected = 0;
|
|
|
|
|
|
|
|
// Find the selected index.
|
2012-08-27 20:04:04 +00:00
|
|
|
$.each( $items, function ( i, e ) {
|
2012-08-15 18:26:11 +00:00
|
|
|
if( $( this ).hasClass( 'selected' ) ) {
|
|
|
|
selected = i;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// Down arrow
|
|
|
|
if ( e.which === 40 ) {
|
|
|
|
e.preventDefault();
|
|
|
|
// If not visible, open and do nothing.
|
|
|
|
if ( !visible ) {
|
|
|
|
open();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
selected = ( selected + 1 ) % $items.length;
|
|
|
|
$items.removeClass( 'selected' );
|
|
|
|
$( $items[selected] ).addClass( 'selected' );
|
|
|
|
// Up Arrow
|
|
|
|
} else if ( e.which === 38 ) {
|
|
|
|
e.preventDefault();
|
|
|
|
// If not visible, open and do nothing.
|
|
|
|
if ( !visible ) {
|
|
|
|
open();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
selected = ( selected + $items.length - 1 ) % $items.length;
|
|
|
|
$items.removeClass( 'selected' );
|
|
|
|
$( $items[selected] ).addClass( 'selected' );
|
2012-08-17 22:13:19 +00:00
|
|
|
// Enter key.
|
|
|
|
} else if ( e.which === 13 ) {
|
|
|
|
// Only if the dropdown is open.
|
|
|
|
if ( visible ) {
|
|
|
|
e.preventDefault();
|
|
|
|
$item = $multiSuggest
|
|
|
|
.find( '.' + options.prefix + '-suggest-item.selected' );
|
|
|
|
if ( $item.length > 0 ) {
|
2012-08-31 20:46:03 +00:00
|
|
|
select.call( this, $item.data( 'text' ) );
|
2012-08-17 22:13:19 +00:00
|
|
|
} else {
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2012-08-15 18:26:11 +00:00
|
|
|
}
|
2012-08-23 22:43:06 +00:00
|
|
|
// Handle normal input.
|
|
|
|
onInput();
|
2012-08-15 18:26:11 +00:00
|
|
|
},
|
2012-08-27 20:04:04 +00:00
|
|
|
'focus': function () {
|
2012-08-15 18:26:11 +00:00
|
|
|
focused = true;
|
|
|
|
open();
|
|
|
|
},
|
2012-08-27 20:04:04 +00:00
|
|
|
'blur': function () {
|
2012-08-15 18:26:11 +00:00
|
|
|
focused = false;
|
|
|
|
close();
|
|
|
|
},
|
2012-08-27 20:04:04 +00:00
|
|
|
'mousedown': function () {
|
2012-08-15 18:26:11 +00:00
|
|
|
if ( focused ) {
|
|
|
|
toggle();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
return this;
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
}( jQuery ) );
|