2012-05-04 02:05:39 +04:00
/ *
2012-05-04 02:00:48 +04:00
Copyright 2012 Igor Vaynberg
2012-06-12 12:40:37 +04:00
2012-05-16 20:35:21 +04:00
Version : @ @ ver @ @ Timestamp : @ @ timestamp @ @
2012-03-29 22:08:14 +04:00
2012-05-04 02:00:48 +04:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ; you may not use this work except in
compliance with the License . You may obtain a copy of the License in the LICENSE file , or at :
2012-03-29 22:08:14 +04:00
2012-05-04 02:00:48 +04:00
http : //www.apache.org/licenses/LICENSE-2.0
2012-05-03 20:01:13 +04:00
2012-05-04 02:00:48 +04:00
Unless required by applicable law or agreed to in writing , software distributed under the License is
distributed on an "AS IS" BASIS , WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and limitations under the License .
* /
2012-06-16 23:00:09 +04:00
( function ( $ ) {
if ( typeof $ . fn . each2 == "undefined" ) {
$ . fn . extend ( {
/ *
* 4 - 10 times faster . each replacement
* use it carefully , as it overrides jQuery context of element on each iteration
* /
each2 : function ( c ) {
var j = $ ( [ 0 ] ) , i = - 1 , l = this . length ;
while (
++ i < l
&& ( j . context = j [ 0 ] = this [ i ] )
&& c . call ( j [ 0 ] , i , j ) !== false //"this"=DOM, i=index, j=jQuery object
) ;
return this ;
}
} ) ;
}
} ) ( jQuery ) ;
2012-04-10 21:42:40 +04:00
( function ( $ , undefined ) {
2012-03-29 22:08:14 +04:00
"use strict" ;
/*global document, window, jQuery, console */
2012-04-01 08:11:04 +04:00
if ( window . Select2 !== undefined ) {
return ;
}
2012-06-16 23:14:46 +04:00
var KEY , AbstractSelect2 , SingleSelect2 , MultiSelect2 , nextUid , sizer ;
2012-04-12 11:33:10 +04:00
KEY = {
2012-03-29 22:08:14 +04:00
TAB : 9 ,
ENTER : 13 ,
ESC : 27 ,
SPACE : 32 ,
LEFT : 37 ,
UP : 38 ,
RIGHT : 39 ,
DOWN : 40 ,
SHIFT : 16 ,
CTRL : 17 ,
ALT : 18 ,
PAGE _UP : 33 ,
PAGE _DOWN : 34 ,
HOME : 36 ,
END : 35 ,
BACKSPACE : 8 ,
DELETE : 46 ,
isArrow : function ( k ) {
k = k . which ? k . which : k ;
switch ( k ) {
case KEY . LEFT :
case KEY . RIGHT :
case KEY . UP :
case KEY . DOWN :
return true ;
}
return false ;
} ,
isControl : function ( k ) {
k = k . which ? k . which : k ;
switch ( k ) {
case KEY . SHIFT :
case KEY . CTRL :
case KEY . ALT :
return true ;
}
return false ;
} ,
isFunctionKey : function ( k ) {
k = k . which ? k . which : k ;
return k >= 112 && k <= 123 ;
}
} ;
2012-06-14 23:16:44 +04:00
nextUid = ( function ( ) { var counter = 1 ; return function ( ) { return counter ++ ; } ; } ( ) ) ;
2012-06-14 11:21:22 +04:00
2012-03-29 22:08:14 +04:00
function indexOf ( value , array ) {
var i = 0 , l = array . length , v ;
2012-06-13 06:41:21 +04:00
if ( typeof value === "undefined" ) {
2012-05-29 16:48:38 +04:00
return - 1 ;
}
2012-03-29 22:08:14 +04:00
if ( value . constructor === String ) {
2012-04-13 19:59:32 +04:00
for ( ; i < l ; i = i + 1 ) if ( value . localeCompare ( array [ i ] ) === 0 ) return i ;
2012-03-29 22:08:14 +04:00
} else {
2012-04-13 19:59:32 +04:00
for ( ; i < l ; i = i + 1 ) {
2012-03-29 22:08:14 +04:00
v = array [ i ] ;
if ( v . constructor === String ) {
if ( v . localeCompare ( value ) === 0 ) return i ;
} else {
if ( v === value ) return i ;
}
}
}
return - 1 ;
}
2012-03-31 01:06:34 +04:00
/ * *
2012-06-14 01:06:37 +04:00
* Compares equality of a and b taking into account that a and b may be strings , in which case localeCompare is used
2012-03-31 01:06:34 +04:00
* @ param a
* @ param b
* /
function equal ( a , b ) {
if ( a === b ) return true ;
if ( a === undefined || b === undefined ) return false ;
if ( a === null || b === null ) return false ;
if ( a . constructor === String ) return a . localeCompare ( b ) === 0 ;
if ( b . constructor === String ) return b . localeCompare ( a ) === 0 ;
return false ;
}
2012-04-13 19:07:59 +04:00
/ * *
* Splits the string into an array of values , trimming each value . An empty array is returned for nulls or empty
* strings
* @ param string
* @ param separator
* /
function splitVal ( string , separator ) {
var val , i , l ;
if ( string === null || string . length < 1 ) return [ ] ;
val = string . split ( separator ) ;
for ( i = 0 , l = val . length ; i < l ; i = i + 1 ) val [ i ] = $ . trim ( val [ i ] ) ;
return val ;
}
2012-03-29 22:08:14 +04:00
function getSideBorderPadding ( element ) {
return element . outerWidth ( ) - element . width ( ) ;
}
function installKeyUpChangeEvent ( element ) {
2012-03-30 02:58:30 +04:00
element . bind ( "keydown" , function ( ) {
2012-03-29 22:08:14 +04:00
element . data ( "keyup-change-value" , element . val ( ) ) ;
} ) ;
2012-03-30 02:58:30 +04:00
element . bind ( "keyup" , function ( ) {
2012-03-29 22:08:14 +04:00
if ( element . val ( ) !== element . data ( "keyup-change-value" ) ) {
element . trigger ( "keyup-change" ) ;
}
} ) ;
}
2012-06-17 11:45:44 +04:00
$ ( document ) . delegate ( "*" , "mousemove" , function ( e ) {
$ ( document ) . data ( "select2-lastpos" , { x : e . pageX , y : e . pageY } ) ;
} ) ;
2012-06-17 11:28:18 +04:00
/ * *
* filters mouse events so an event is fired only if the mouse moved .
*
* filters out mouse events that occur when mouse is stationary but
* the elements under the pointer are scrolled .
* /
function installFilteredMouseMove ( element ) {
2012-06-17 11:45:44 +04:00
element . bind ( "mousemove" , function ( e ) {
var lastpos = $ ( document ) . data ( "select2-lastpos" ) ;
2012-06-17 11:28:18 +04:00
if ( lastpos === undefined || lastpos . x !== e . pageX || lastpos . y !== e . pageY ) {
$ ( e . target ) . trigger ( "mousemove-filtered" , e ) ;
}
} ) ;
}
/ * *
2012-04-01 08:11:04 +04:00
* Debounces a function . Returns a function that calls the original fn function only if no invocations have been made
* within the last quietMillis milliseconds .
*
* @ param quietMillis number of milliseconds to wait before invoking fn
* @ param fn function to be debounced
* @ return debounced version of fn
* /
function debounce ( quietMillis , fn ) {
2012-03-29 22:08:14 +04:00
var timeout ;
return function ( ) {
window . clearTimeout ( timeout ) ;
2012-04-01 08:11:04 +04:00
timeout = window . setTimeout ( fn , quietMillis ) ;
2012-03-29 22:08:14 +04:00
} ;
}
function installDebouncedScroll ( threshold , element ) {
var notify = debounce ( threshold , function ( e ) { element . trigger ( "scroll-debounced" , e ) ; } ) ;
2012-03-30 02:58:30 +04:00
element . bind ( "scroll" , function ( e ) {
2012-03-29 22:08:14 +04:00
if ( indexOf ( e . target , element . get ( ) ) >= 0 ) notify ( e ) ;
} ) ;
}
function killEvent ( event ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
}
2012-06-16 23:23:12 +04:00
function measureTextWidth ( e ) {
2012-06-16 23:14:46 +04:00
if ( ! sizer ) {
2012-06-17 00:30:22 +04:00
var style = e [ 0 ] . currentStyle || window . getComputedStyle ( e [ 0 ] , null ) ;
2012-06-16 23:14:46 +04:00
sizer = $ ( "<div></div>" ) . css ( {
position : "absolute" ,
left : "-1000px" ,
top : "-1000px" ,
display : "none" ,
fontSize : style . fontSize ,
fontFamily : style . fontFamily ,
fontStyle : style . fontStyle ,
fontWeight : style . fontWeight ,
letterSpacing : style . letterSpacing ,
textTransform : style . textTransform ,
whiteSpace : "nowrap"
} ) ;
$ ( "body" ) . append ( sizer ) ;
}
sizer . text ( e . val ( ) ) ;
return sizer . width ( ) ;
2012-03-29 22:08:14 +04:00
}
2012-04-01 08:11:04 +04:00
/ * *
* Produces an ajax - based query function
*
* @ param options object containing configuration paramters
2012-04-11 23:17:58 +04:00
* @ param options . transport function that will be used to execute the ajax request . must be compatible with parameters supported by $ . ajax
* @ param options . url url for the data
2012-06-05 04:15:11 +04:00
* @ param options . data a function ( searchTerm , pageNumber , context ) that should return an object containing query string parameters for the above url .
2012-04-11 23:17:58 +04:00
* @ param options . dataType request data type : ajax , jsonp , other datatatypes supported by jQuery ' s $ . ajax function or the transport function if specified
2012-04-01 08:11:04 +04:00
* @ param options . quietMillis ( optional ) milliseconds to wait before making the ajaxRequest , helps debounce the ajax function if invoked too often
* @ param options . results a function ( remoteData , pageNumber ) that converts data returned form the remote request to the format expected by Select2 .
* The expected format is an object containing the following keys :
* results array of objects that will be used as choices
* more ( optional ) boolean indicating whether there are more results available
* Example : { results : [ { id : 1 , text : 'Red' } , { id : 2 , text : 'Blue' } ] , more : true }
* /
function ajax ( options ) {
var timeout , // current scheduled but not yet executed request
requestSequence = 0 , // sequence used to drop out-of-order responses
2012-05-06 17:37:51 +04:00
handler = null ,
2012-04-01 08:11:04 +04:00
quietMillis = options . quietMillis || 100 ;
return function ( query ) {
window . clearTimeout ( timeout ) ;
timeout = window . setTimeout ( function ( ) {
requestSequence += 1 ; // increment the sequence
var requestNumber = requestSequence , // this request's sequence number
2012-04-11 23:17:58 +04:00
data = options . data , // ajax data function
2012-04-12 10:28:54 +04:00
transport = options . transport || $ . ajax ;
2012-04-01 08:11:04 +04:00
2012-06-05 04:15:11 +04:00
data = data . call ( this , query . term , query . page , query . context ) ;
2012-04-01 08:11:04 +04:00
2012-05-06 17:37:51 +04:00
if ( null !== handler ) {
handler . abort ( ) ;
}
handler = transport . call ( null , {
2012-04-01 08:11:04 +04:00
url : options . url ,
dataType : options . dataType ,
data : data ,
success : function ( data ) {
if ( requestNumber < requestSequence ) {
return ;
}
2012-04-13 22:29:41 +04:00
// TODO 3.0 - replace query.page with query so users have access to term, page, etc.
Adds an additional parameter roundtripValue to function data.
Enables a stored value, which can be passed by the function 'result' to the next call of the function 'data'.
This is a solution for issue #72
Example:
data: function (term, page, roundtripValue) {
var options = {
q: term,
limit: 5,
};
if (typeof(roundtripValue) !== 'undefined' && roundtripValue != null) {
options['continuation_handle'] = roundtripValue;
}
return options;
},
results: function (data, page) {
var roundtripValue = data.continuation_handle;
var more = typeof(roundtripValue) !== 'undefined';
return {results: data.results, more: more, roundtripValue: roundtripValue};
},
Signed-off-by: Igor Vaynberg <igor.vaynberg@gmail.com>
2012-05-31 19:56:01 +04:00
var results = options . results ( data , query . page ) ;
query . callback ( results ) ;
2012-04-01 08:11:04 +04:00
}
} ) ;
} , quietMillis ) ;
} ;
}
/ * *
* Produces a query function that works with a local array
*
* @ param options object containing configuration parameters . The options parameter can either be an array or an
* object .
*
* If the array form is used it is assumed that it contains objects with 'id' and 'text' keys .
*
* If the object form is used ti is assumed that it contains 'data' and 'text' keys . The 'data' key should contain
* an array of objects that will be used as choices . These objects must contain at least an 'id' key . The 'text'
* key can either be a String in which case it is expected that each element in the 'data' array has a key with the
* value of 'text' which will be used to match choices . Alternatively , text can be a function ( item ) that can extract
* the text .
* /
function local ( options ) {
2012-06-14 20:39:23 +04:00
var data = options , // data elements
dataText ,
2012-05-29 02:15:51 +04:00
text = function ( item ) { return "" + item . text ; } ; // function used to retrieve the text portion of a data item that is matched against the search
2012-04-01 08:11:04 +04:00
if ( ! $ . isArray ( data ) ) {
text = data . text ;
// if text is not a function we assume it to be a key name
2012-06-13 15:09:43 +04:00
if ( ! $ . isFunction ( text ) ) {
2012-06-14 20:39:23 +04:00
dataText = data . text ; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
2012-06-13 15:09:43 +04:00
text = function ( item ) { return item [ dataText ] ; } ;
}
2012-04-01 08:11:04 +04:00
data = data . results ;
}
return function ( query ) {
2012-06-08 07:47:33 +04:00
var t = query . term , filtered = { } ;
2012-04-01 08:11:04 +04:00
if ( t === "" ) {
query . callback ( { results : data } ) ;
return ;
}
filtered . results = $ ( data )
2012-06-08 07:47:33 +04:00
. filter ( function ( ) { return query . matcher ( t , text ( this ) ) ; } )
2012-04-01 08:11:04 +04:00
. get ( ) ;
query . callback ( filtered ) ;
} ;
}
2012-04-03 09:27:58 +04:00
// TODO javadoc
function tags ( data ) {
2012-04-27 22:40:32 +04:00
// TODO even for a function we should probably return a wrapper that does the same object/string check as
// the function for arrays. otherwise only functions that return objects are supported.
2012-04-03 09:27:58 +04:00
if ( $ . isFunction ( data ) ) {
return data ;
}
// if not a function we assume it to be an array
return function ( query ) {
2012-06-08 07:47:33 +04:00
var t = query . term , filtered = { results : [ ] } ;
2012-04-03 09:27:58 +04:00
$ ( data ) . each ( function ( ) {
2012-05-04 02:00:48 +04:00
var isObject = this . text !== undefined ,
2012-04-27 22:40:32 +04:00
text = isObject ? this . text : this ;
2012-06-08 07:47:33 +04:00
if ( t === "" || query . matcher ( t , text ) ) {
2012-04-27 22:40:32 +04:00
filtered . results . push ( isObject ? this : { id : this , text : this } ) ;
}
2012-04-03 09:27:58 +04:00
} ) ;
query . callback ( filtered ) ;
2012-04-13 19:59:32 +04:00
} ;
2012-04-03 09:27:58 +04:00
}
2012-03-29 22:08:14 +04:00
/ * *
* blurs any Select2 container that has focus when an element outside them was clicked or received focus
* /
$ ( document ) . ready ( function ( ) {
2012-06-08 10:56:51 +04:00
$ ( document ) . delegate ( "*" , "mousedown focusin touchend" , function ( e ) {
2012-03-29 22:08:14 +04:00
var target = $ ( e . target ) . closest ( "div.select2-container" ) . get ( 0 ) ;
2012-06-12 12:40:37 +04:00
if ( target ) {
$ ( document ) . find ( "div.select2-container-active" ) . each ( function ( ) {
if ( this !== target ) $ ( this ) . data ( "select2" ) . blur ( ) ;
} ) ;
} else {
target = $ ( e . target ) . closest ( "div.select2-drop" ) . get ( 0 ) ;
$ ( document ) . find ( "div.select2-drop-active" ) . each ( function ( ) {
if ( this !== target ) $ ( this ) . data ( "select2" ) . blur ( ) ;
} ) ;
}
2012-03-29 22:08:14 +04:00
} ) ;
} ) ;
/ * *
2012-04-12 11:33:10 +04:00
* Creates a new class
2012-03-29 22:08:14 +04:00
*
2012-04-12 11:33:10 +04:00
* @ param superClass
* @ param methods
2012-03-29 22:08:14 +04:00
* /
2012-04-13 19:59:32 +04:00
function clazz ( SuperClass , methods ) {
var constructor = function ( ) { } ;
constructor . prototype = new SuperClass ;
constructor . prototype . constructor = constructor ;
constructor . prototype . parent = SuperClass . prototype ;
constructor . prototype = $ . extend ( constructor . prototype , methods ) ;
return constructor ;
2012-03-29 22:08:14 +04:00
}
2012-04-12 11:33:10 +04:00
AbstractSelect2 = clazz ( Object , {
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
bind : function ( func ) {
var self = this ;
return function ( ) {
func . apply ( self , arguments ) ;
} ;
} ,
2012-04-12 10:28:54 +04:00
2012-04-12 11:33:10 +04:00
init : function ( opts ) {
var results , search , resultsSelector = ".select2-results" ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// prepare options
2012-05-04 02:00:48 +04:00
this . opts = opts = this . prepareOpts ( opts ) ;
this . id = opts . id ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// destroy if called on an existing component
2012-05-22 20:40:42 +04:00
if ( opts . element . data ( "select2" ) !== undefined &&
opts . element . data ( "select2" ) !== null ) {
2012-04-12 11:33:10 +04:00
this . destroy ( ) ;
}
2012-03-29 22:08:14 +04:00
2012-06-07 08:52:08 +04:00
this . enabled = true ;
2012-04-12 11:33:10 +04:00
this . container = this . createContainer ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( opts . element . attr ( "class" ) !== undefined ) {
this . container . addClass ( opts . element . attr ( "class" ) ) ;
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// swap container for the element
this . opts . element
. data ( "select2" , this )
. hide ( )
. after ( this . container ) ;
this . container . data ( "select2" , this ) ;
this . dropdown = this . container . find ( ".select2-drop" ) ;
2012-06-12 12:40:37 +04:00
this . dropdown . data ( "select2" , this ) ;
2012-04-12 11:33:10 +04:00
this . results = results = this . container . find ( resultsSelector ) ;
this . search = search = this . container . find ( "input[type=text]" ) ;
this . resultsPage = 0 ;
2012-06-05 04:15:11 +04:00
this . context = null ;
2012-04-12 11:33:10 +04:00
// initialize the container
this . initContainer ( ) ;
2012-06-17 11:45:44 +04:00
installFilteredMouseMove ( this . results ) ;
this . dropdown . delegate ( resultsSelector , "mousemove-filtered" , this . bind ( this . highlightUnderEvent ) ) ;
2012-04-12 11:33:10 +04:00
installDebouncedScroll ( 80 , this . results ) ;
2012-06-12 12:40:37 +04:00
this . dropdown . delegate ( resultsSelector , "scroll-debounced" , this . bind ( this . loadMoreIfNeeded ) ) ;
2012-04-12 11:33:10 +04:00
// if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
if ( $ . fn . mousewheel ) {
results . mousewheel ( function ( e , delta , deltaX , deltaY ) {
var top = results . scrollTop ( ) , height ;
if ( deltaY > 0 && top - deltaY <= 0 ) {
results . scrollTop ( 0 ) ;
killEvent ( e ) ;
} else if ( deltaY < 0 && results . get ( 0 ) . scrollHeight - results . scrollTop ( ) + deltaY <= results . height ( ) ) {
results . scrollTop ( results . get ( 0 ) . scrollHeight - results . height ( ) ) ;
killEvent ( e ) ;
}
} ) ;
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
installKeyUpChangeEvent ( search ) ;
search . bind ( "keyup-change" , this . bind ( this . updateResults ) ) ;
2012-05-04 02:00:48 +04:00
search . bind ( "focus" , function ( ) { search . addClass ( "select2-focused" ) ; } ) ;
search . bind ( "blur" , function ( ) { search . removeClass ( "select2-focused" ) ; } ) ;
2012-03-29 22:08:14 +04:00
2012-06-12 12:40:37 +04:00
this . dropdown . delegate ( resultsSelector , "click" , this . bind ( function ( e ) {
2012-04-12 11:33:10 +04:00
if ( $ ( e . target ) . closest ( ".select2-result:not(.select2-disabled)" ) . length > 0 ) {
this . highlightUnderEvent ( e ) ;
this . selectHighlighted ( e ) ;
} else {
2012-03-29 22:08:14 +04:00
killEvent ( e ) ;
2012-04-12 11:33:10 +04:00
this . focusSearch ( ) ;
2012-03-29 22:08:14 +04:00
}
2012-04-12 11:33:10 +04:00
} ) ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( $ . isFunction ( this . opts . initSelection ) ) {
// initialize selection based on the current value of the source element
this . initSelection ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// if the user has provided a function that can set selection based on the value of the source element
// we monitor the change event on the element and trigger it, allowing for two way synchronization
this . monitorSource ( ) ;
2012-03-29 22:08:14 +04:00
}
2012-06-07 08:52:08 +04:00
if ( opts . element . is ( ":disabled" ) ) this . disable ( ) ;
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
destroy : function ( ) {
var select2 = this . opts . element . data ( "select2" ) ;
if ( select2 !== undefined ) {
select2 . container . remove ( ) ;
2012-06-12 12:40:37 +04:00
select2 . dropdown . remove ( ) ;
2012-04-12 11:33:10 +04:00
select2 . opts . element
. removeData ( "select2" )
2012-04-17 19:16:22 +04:00
. unbind ( ".select2" )
2012-06-16 23:14:46 +04:00
. show ( ) ;
2012-04-12 11:33:10 +04:00
}
} ,
2012-04-12 10:28:54 +04:00
2012-04-12 11:33:10 +04:00
prepareOpts : function ( opts ) {
2012-05-04 02:00:48 +04:00
var element , select , idKey ;
2012-06-17 17:47:56 +04:00
opts . elementBody = opts . element . closest ( "body" ) ; // cache for future access
2012-03-29 22:08:14 +04:00
2012-05-07 20:24:14 +04:00
element = opts . element ;
if ( element . get ( 0 ) . tagName . toLowerCase ( ) === "select" ) {
this . select = select = opts . element ;
}
if ( select ) {
// these options are not allowed when attached to a select because they are picked up off the element itself
$ . each ( [ "id" , "multiple" , "ajax" , "query" , "createSearchChoice" , "initSelection" , "data" , "tags" ] , function ( ) {
if ( this in opts ) {
throw new Error ( "Option '" + this + "' is not allowed for Select2 when attached to a <select> element." ) ;
}
} ) ;
}
2012-04-12 11:33:10 +04:00
opts = $ . extend ( { } , {
2012-06-14 11:21:22 +04:00
populateResults : function ( container , results ) {
var uidToData = { } , populate , markup = [ ] , uid , data , result , children ;
populate = function ( results , depth ) {
var i , l , uid , result , selectable , compound ;
for ( i = 0 , l = results . length ; i < l ; i = i + 1 ) {
2012-06-12 15:35:56 +04:00
2012-06-14 11:21:22 +04:00
result = results [ i ] ;
selectable = ( "id" in result ) ; // TODO switch to id() function
compound = ( "children" in result ) && result . children . length > 0 ;
2012-06-12 15:35:56 +04:00
2012-06-14 11:21:22 +04:00
markup . push ( "<li class='select2-result-depth-" + depth ) ;
if ( ! selectable ) { markup . push ( " select2-result-unselectable" ) ; } else { markup . push ( " select2-result" ) ; }
if ( compound ) { markup . push ( " select2-result-with-children" ) ; }
markup . push ( "'" ) ;
if ( selectable ) {
uid = nextUid ( ) ;
markup . push ( " id='select2-result-" + uid + "'" ) ;
uidToData [ uid ] = result ;
2012-06-12 15:35:56 +04:00
}
2012-06-14 11:21:22 +04:00
markup . push ( "><div class='select2-result-label'>" + opts . formatResult ( result ) + "</div>" ) ;
2012-06-12 15:35:56 +04:00
2012-06-14 11:21:22 +04:00
if ( compound ) {
markup . push ( "<ul class='select2-result-sub'>" ) ;
populate ( result . children , depth + 1 ) ;
markup . push ( "</ul>" ) ;
}
2012-06-12 15:35:56 +04:00
2012-06-14 11:21:22 +04:00
markup . push ( "</li>" ) ;
}
2012-06-12 15:35:56 +04:00
} ;
2012-06-14 11:21:22 +04:00
populate ( results , 0 ) ;
children = container . children ( ) ;
2012-06-14 23:16:44 +04:00
if ( children . length === 0 ) {
2012-06-14 11:21:22 +04:00
container . html ( markup . join ( "" ) ) ;
} else {
$ ( children [ children . length - 1 ] ) . append ( markup . join ( "" ) ) ;
}
for ( uid in uidToData ) {
2012-06-16 22:11:38 +04:00
$ ( "#select2-result-" + uid , container ) . data ( "select2-data" , uidToData [ uid ] ) ;
2012-06-14 11:21:22 +04:00
}
} ,
formatResult : function ( result ) {
return result . text ;
2012-06-12 15:35:56 +04:00
} ,
formatSelection : function ( data ) {
2012-06-16 23:00:09 +04:00
return data . fullText || data . text ;
2012-06-12 15:35:56 +04:00
} ,
2012-04-12 11:33:10 +04:00
formatNoMatches : function ( ) { return "No matches found" ; } ,
formatInputTooShort : function ( input , min ) { return "Please enter " + ( min - input . length ) + " more characters" ; } ,
2012-05-04 02:00:48 +04:00
minimumResultsForSearch : 0 ,
2012-05-07 20:24:14 +04:00
minimumInputLength : 0 ,
2012-06-08 07:47:33 +04:00
id : function ( e ) { return e . id ; } ,
matcher : function ( term , text ) {
return text . toUpperCase ( ) . indexOf ( term . toUpperCase ( ) ) >= 0 ;
}
2012-04-12 11:33:10 +04:00
} , opts ) ;
2012-03-29 22:08:14 +04:00
2012-05-04 02:00:48 +04:00
if ( typeof ( opts . id ) !== "function" ) {
idKey = opts . id ;
2012-05-07 20:24:14 +04:00
opts . id = function ( e ) { return e [ idKey ] ; } ;
2012-04-12 11:33:10 +04:00
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( select ) {
opts . query = this . bind ( function ( query ) {
2012-06-14 11:21:22 +04:00
var data = { results : [ ] , more : false } ,
2012-06-08 07:47:33 +04:00
term = query . term ,
2012-06-14 11:21:22 +04:00
process ;
2012-03-29 22:08:14 +04:00
2012-06-14 11:21:22 +04:00
process = function ( element , collection ) {
var group ;
if ( element . is ( "option" ) ) {
if ( query . matcher ( term , element . text ( ) ) ) {
collection . push ( { id : element . attr ( "value" ) , text : element . text ( ) } ) ;
}
} else if ( element . is ( "optgroup" ) ) {
group = { text : element . attr ( "label" ) , children : [ ] } ;
2012-06-16 23:00:09 +04:00
element . children ( ) . each2 ( function ( i , elm ) { process ( elm , group . children ) ; } ) ;
2012-06-14 11:21:22 +04:00
if ( group . children . length > 0 ) {
collection . push ( group ) ;
2012-06-12 15:35:56 +04:00
}
}
2012-06-14 11:21:22 +04:00
} ;
2012-03-29 22:08:14 +04:00
2012-06-16 23:00:09 +04:00
element . children ( ) . each2 ( function ( i , elm ) { process ( elm , data . results ) ; } ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
query . callback ( data ) ;
2012-03-29 22:08:14 +04:00
} ) ;
2012-05-04 02:05:39 +04:00
// this is needed because inside val() we construct choices from options and there id is hardcoded
2012-05-04 02:00:48 +04:00
opts . id = function ( e ) { return e . id ; } ;
2012-04-12 11:33:10 +04:00
} else {
if ( ! ( "query" in opts ) ) {
if ( "ajax" in opts ) {
opts . query = ajax ( opts . ajax ) ;
} else if ( "data" in opts ) {
opts . query = local ( opts . data ) ;
} else if ( "tags" in opts ) {
opts . query = tags ( opts . tags ) ;
2012-04-13 19:59:32 +04:00
opts . createSearchChoice = function ( term ) { return { id : term , text : term } ; } ;
2012-04-12 11:33:10 +04:00
opts . initSelection = function ( element ) {
var data = [ ] ;
2012-04-13 19:07:59 +04:00
$ ( splitVal ( element . val ( ) , "," ) ) . each ( function ( ) {
2012-04-12 11:33:10 +04:00
data . push ( { id : this , text : this } ) ;
} ) ;
return data ;
2012-04-13 19:07:59 +04:00
} ;
2012-04-10 18:31:59 +04:00
}
2012-03-29 22:08:14 +04:00
}
}
2012-04-12 11:33:10 +04:00
if ( typeof ( opts . query ) !== "function" ) {
throw "query function not defined for Select2 " + opts . element . attr ( "id" ) ;
2012-04-11 11:51:27 +04:00
}
2012-03-31 01:06:34 +04:00
2012-04-12 11:33:10 +04:00
return opts ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
/ * *
* Monitor the original element for changes and update select2 accordingly
* /
monitorSource : function ( ) {
2012-04-17 19:16:22 +04:00
this . opts . element . bind ( "change.select2" , this . bind ( function ( e ) {
2012-04-12 11:33:10 +04:00
if ( this . opts . element . data ( "select2-change-triggered" ) !== true ) {
this . initSelection ( ) ;
}
} ) ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
/ * *
* Triggers the change event on the source element
* /
triggerChange : function ( ) {
// Prevents recursive triggering
this . opts . element . data ( "select2-change-triggered" , true ) ;
this . opts . element . trigger ( "change" ) ;
this . opts . element . data ( "select2-change-triggered" , false ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-06-07 08:52:08 +04:00
enable : function ( ) {
if ( this . enabled ) return ;
this . enabled = true ;
this . container . removeClass ( "select2-container-disabled" ) ;
} ,
disable : function ( ) {
if ( ! this . enabled ) return ;
this . close ( ) ;
this . enabled = false ;
this . container . addClass ( "select2-container-disabled" ) ;
} ,
2012-04-12 11:33:10 +04:00
opened : function ( ) {
return this . container . hasClass ( "select2-dropdown-open" ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-06-13 08:59:39 +04:00
positionDropdown : function ( ) {
2012-06-12 12:40:37 +04:00
var offset = this . container . offset ( ) ;
var height = this . container . outerHeight ( ) ;
var width = this . container . outerWidth ( ) ;
2012-06-14 20:03:38 +04:00
var css = {
2012-06-12 12:40:37 +04:00
top : offset . top + height ,
left : offset . left ,
2012-06-14 20:03:38 +04:00
width : width
}
if ( this . opts . dropdownZIndex !== undefined ) {
2012-06-14 23:16:44 +04:00
css [ "z-index" ] = this . opts . dropdownZIndex ;
2012-06-14 20:03:38 +04:00
}
this . dropdown . css ( css ) ;
2012-06-12 12:40:37 +04:00
} ,
2012-04-12 11:33:10 +04:00
open : function ( ) {
if ( this . opened ( ) ) return ;
2012-06-17 16:38:00 +04:00
this . clearPlaceholder ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . container . addClass ( "select2-dropdown-open" ) . addClass ( "select2-container-active" ) ;
2012-06-17 17:47:56 +04:00
if ( this . dropdown [ 0 ] !== this . opts . elementBody . children ( ) . last ( ) [ 0 ] ) { // ensure our dropdown is the last eleemnt, so the z-index is always respected correctly
this . dropdown . detach ( ) . appendTo ( this . opts . elementBody ) ;
}
this . dropdown . addClass ( "select2-drop-active" ) ;
2012-06-12 12:40:37 +04:00
2012-06-13 08:59:39 +04:00
this . positionDropdown ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . updateResults ( true ) ;
this . dropdown . show ( ) ;
2012-05-19 01:50:35 +04:00
this . ensureHighlightVisible ( ) ;
2012-04-12 11:33:10 +04:00
this . focusSearch ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
close : function ( ) {
if ( ! this . opened ( ) ) return ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . dropdown . hide ( ) ;
this . container . removeClass ( "select2-dropdown-open" ) ;
this . results . empty ( ) ;
this . clearSearch ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
clearSearch : function ( ) {
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
ensureHighlightVisible : function ( ) {
var results = this . results , children , index , child , hb , rb , y , more ;
2012-06-16 23:00:09 +04:00
2012-04-12 11:33:10 +04:00
index = this . highlight ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( index < 0 ) return ;
2012-06-16 23:00:09 +04:00
children = results . find ( ".select2-result" ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
child = $ ( children [ index ] ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
hb = child . offset ( ) . top + child . outerHeight ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// if this is the last child lets also make sure select2-more-results is visible
if ( index === children . length - 1 ) {
more = results . find ( "li.select2-more-results" ) ;
if ( more . length > 0 ) {
hb = more . offset ( ) . top + more . outerHeight ( ) ;
}
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
rb = results . offset ( ) . top + results . outerHeight ( ) ;
if ( hb > rb ) {
results . scrollTop ( results . scrollTop ( ) + ( hb - rb ) ) ;
2012-03-29 22:08:14 +04:00
}
2012-04-12 11:33:10 +04:00
y = child . offset ( ) . top - results . offset ( ) . top ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// make sure the top of the element is visible
if ( y < 0 ) {
results . scrollTop ( results . scrollTop ( ) + y ) ; // y is negative
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
moveHighlight : function ( delta ) {
2012-06-12 15:35:56 +04:00
var choices = this . results . find ( ".select2-result" ) ,
2012-04-12 11:33:10 +04:00
index = this . highlight ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
while ( index > - 1 && index < choices . length ) {
index += delta ;
if ( ! $ ( choices [ index ] ) . hasClass ( "select2-disabled" ) ) {
this . highlight ( index ) ;
break ;
}
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
highlight : function ( index ) {
2012-06-12 15:35:56 +04:00
var choices = this . results . find ( ".select2-result .select2-result-label" ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( arguments . length === 0 ) {
return indexOf ( choices . filter ( ".select2-highlighted" ) [ 0 ] , choices . get ( ) ) ;
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( index >= choices . length ) index = choices . length - 1 ;
if ( index < 0 ) index = 0 ;
2012-03-29 22:08:14 +04:00
2012-06-12 15:35:56 +04:00
if ( $ ( choices [ index ] ) . parent ( ) . is ( '.select2-result-unselectable' ) ) {
return ;
}
choices . removeClass ( "select2-highlighted" ) ;
2012-04-12 11:33:10 +04:00
$ ( choices [ index ] ) . addClass ( "select2-highlighted" ) ;
this . ensureHighlightVisible ( ) ;
2012-03-29 22:08:14 +04:00
2012-06-17 01:31:24 +04:00
//if (this.opened()) this.focusSearch();
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
highlightUnderEvent : function ( event ) {
2012-06-16 23:00:09 +04:00
var el = $ ( event . target ) . closest ( ".select2-result" ) ;
2012-04-12 11:33:10 +04:00
if ( el . length > 0 ) {
2012-06-17 01:31:24 +04:00
var choices = this . results . find ( '.select2-result' ) ;
2012-06-12 15:35:56 +04:00
this . highlight ( choices . index ( el ) ) ;
2012-04-12 11:33:10 +04:00
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
loadMoreIfNeeded : function ( ) {
var results = this . results ,
more = results . find ( "li.select2-more-results" ) ,
below , // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
offset = - 1 , // index of first element without data
2012-06-14 11:21:22 +04:00
page = this . resultsPage + 1 ,
self = this ;
2012-04-12 11:33:10 +04:00
if ( more . length === 0 ) return ;
below = more . offset ( ) . top - results . offset ( ) . top - results . height ( ) ;
if ( below <= 0 ) {
more . addClass ( "select2-active" ) ;
Adds an additional parameter roundtripValue to function data.
Enables a stored value, which can be passed by the function 'result' to the next call of the function 'data'.
This is a solution for issue #72
Example:
data: function (term, page, roundtripValue) {
var options = {
q: term,
limit: 5,
};
if (typeof(roundtripValue) !== 'undefined' && roundtripValue != null) {
options['continuation_handle'] = roundtripValue;
}
return options;
},
results: function (data, page) {
var roundtripValue = data.continuation_handle;
var more = typeof(roundtripValue) !== 'undefined';
return {results: data.results, more: more, roundtripValue: roundtripValue};
},
Signed-off-by: Igor Vaynberg <igor.vaynberg@gmail.com>
2012-05-31 19:56:01 +04:00
this . opts . query ( {
term : this . search . val ( ) ,
page : page ,
2012-06-13 06:41:21 +04:00
context : this . context ,
matcher : this . opts . matcher ,
Adds an additional parameter roundtripValue to function data.
Enables a stored value, which can be passed by the function 'result' to the next call of the function 'data'.
This is a solution for issue #72
Example:
data: function (term, page, roundtripValue) {
var options = {
q: term,
limit: 5,
};
if (typeof(roundtripValue) !== 'undefined' && roundtripValue != null) {
options['continuation_handle'] = roundtripValue;
}
return options;
},
results: function (data, page) {
var roundtripValue = data.continuation_handle;
var more = typeof(roundtripValue) !== 'undefined';
return {results: data.results, more: more, roundtripValue: roundtripValue};
},
Signed-off-by: Igor Vaynberg <igor.vaynberg@gmail.com>
2012-05-31 19:56:01 +04:00
callback : this . bind ( function ( data ) {
2012-06-14 11:21:22 +04:00
self . opts . populateResults ( results , data . results ) ;
if ( data . more === true ) {
more . detach ( ) ;
results . children ( ) . filter ( ":last" ) . append ( more ) ;
2012-04-12 11:33:10 +04:00
more . removeClass ( "select2-active" ) ;
2012-03-29 22:08:14 +04:00
} else {
2012-04-12 11:33:10 +04:00
more . remove ( ) ;
2012-03-29 22:08:14 +04:00
}
2012-06-14 11:21:22 +04:00
self . resultsPage = page ;
2012-04-12 11:33:10 +04:00
} ) } ) ;
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
/ * *
* @ param initial whether or not this is the call to this method right after the dropdown has been opened
* /
updateResults : function ( initial ) {
2012-05-04 02:00:48 +04:00
var search = this . search , results = this . results , opts = this . opts , self = this ;
2012-03-29 22:08:14 +04:00
2012-06-08 10:09:51 +04:00
// if the search is currently hidden we do not alter the results
if ( initial !== true && this . showSearchInput === false ) {
return ;
}
2012-04-12 11:33:10 +04:00
search . addClass ( "select2-active" ) ;
2012-03-29 22:08:14 +04:00
2012-06-14 11:21:22 +04:00
function postRender ( ) {
2012-04-12 11:33:10 +04:00
results . scrollTop ( 0 ) ;
search . removeClass ( "select2-active" ) ;
2012-04-03 09:27:58 +04:00
}
2012-03-29 22:08:14 +04:00
2012-06-14 11:21:22 +04:00
function render ( html ) {
results . html ( html ) ;
postRender ( ) ;
}
2012-04-12 11:33:10 +04:00
if ( search . val ( ) . length < opts . minimumInputLength ) {
render ( "<li class='select2-no-results'>" + opts . formatInputTooShort ( search . val ( ) , opts . minimumInputLength ) + "</li>" ) ;
2012-03-29 22:08:14 +04:00
return ;
}
2012-04-12 11:33:10 +04:00
this . resultsPage = 1 ;
Adds an additional parameter roundtripValue to function data.
Enables a stored value, which can be passed by the function 'result' to the next call of the function 'data'.
This is a solution for issue #72
Example:
data: function (term, page, roundtripValue) {
var options = {
q: term,
limit: 5,
};
if (typeof(roundtripValue) !== 'undefined' && roundtripValue != null) {
options['continuation_handle'] = roundtripValue;
}
return options;
},
results: function (data, page) {
var roundtripValue = data.continuation_handle;
var more = typeof(roundtripValue) !== 'undefined';
return {results: data.results, more: more, roundtripValue: roundtripValue};
},
Signed-off-by: Igor Vaynberg <igor.vaynberg@gmail.com>
2012-05-31 19:56:01 +04:00
opts . query ( {
term : search . val ( ) ,
page : this . resultsPage ,
2012-06-05 04:15:11 +04:00
context : null ,
2012-06-08 07:47:33 +04:00
matcher : opts . matcher ,
Adds an additional parameter roundtripValue to function data.
Enables a stored value, which can be passed by the function 'result' to the next call of the function 'data'.
This is a solution for issue #72
Example:
data: function (term, page, roundtripValue) {
var options = {
q: term,
limit: 5,
};
if (typeof(roundtripValue) !== 'undefined' && roundtripValue != null) {
options['continuation_handle'] = roundtripValue;
}
return options;
},
results: function (data, page) {
var roundtripValue = data.continuation_handle;
var more = typeof(roundtripValue) !== 'undefined';
return {results: data.results, more: more, roundtripValue: roundtripValue};
},
Signed-off-by: Igor Vaynberg <igor.vaynberg@gmail.com>
2012-05-31 19:56:01 +04:00
callback : this . bind ( function ( data ) {
2012-06-14 11:21:22 +04:00
var def ; // default choice
2012-04-12 11:33:10 +04:00
2012-06-13 06:41:21 +04:00
// save context, if any
this . context = ( data . context === undefined ) ? null : data . context ;
2012-04-12 11:33:10 +04:00
// create a default choice and prepend it to the list
if ( this . opts . createSearchChoice && search . val ( ) !== "" ) {
def = this . opts . createSearchChoice . call ( null , search . val ( ) , data . results ) ;
2012-05-04 02:00:48 +04:00
if ( def !== undefined && def !== null && self . id ( def ) !== undefined && self . id ( def ) !== null ) {
2012-04-12 11:33:10 +04:00
if ( $ ( data . results ) . filter (
function ( ) {
2012-05-04 02:00:48 +04:00
return equal ( self . id ( this ) , self . id ( def ) ) ;
2012-04-12 11:33:10 +04:00
} ) . length === 0 ) {
data . results . unshift ( def ) ;
}
}
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( data . results . length === 0 ) {
render ( "<li class='select2-no-results'>" + opts . formatNoMatches ( search . val ( ) ) + "</li>" ) ;
return ;
}
2012-03-29 22:08:14 +04:00
2012-06-14 11:21:22 +04:00
results . empty ( ) ;
self . opts . populateResults ( results , data . results ) ;
postRender ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( data . more === true ) {
2012-06-14 11:21:22 +04:00
results . children ( ) . filter ( ":last" ) . append ( "<li class='select2-more-results'>Loading more results...</li>" ) ;
2012-04-12 11:33:10 +04:00
}
this . postprocessResults ( data , initial ) ;
} ) } ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
cancel : function ( ) {
2012-03-29 22:08:14 +04:00
this . close ( ) ;
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
blur : function ( ) {
/ * w e d o t h i s i n a t i m e o u t s o t h a t c u r r e n t e v e n t p r o c e s s i n g c a n c o m p l e t e b e f o r e t h i s c o d e i s e x e c u t e d .
this allows tab index to be preserved even if this code blurs the textfield * /
window . setTimeout ( this . bind ( function ( ) {
this . close ( ) ;
this . container . removeClass ( "select2-container-active" ) ;
2012-06-12 12:40:37 +04:00
this . dropdown . removeClass ( "select2-drop-active" ) ;
2012-04-12 11:33:10 +04:00
this . clearSearch ( ) ;
this . selection . find ( ".select2-search-choice-focus" ) . removeClass ( "select2-search-choice-focus" ) ;
this . search . blur ( ) ;
} ) , 10 ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
focusSearch : function ( ) {
/ * w e d o t h i s i n a t i m e o u t s o t h a t c u r r e n t e v e n t p r o c e s s i n g c a n c o m p l e t e b e f o r e t h i s c o d e i s e x e c u t e d .
this makes sure the search field is focussed even if the current event would blur it * /
window . setTimeout ( this . bind ( function ( ) {
this . search . focus ( ) ;
} ) , 10 ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
selectHighlighted : function ( ) {
2012-06-12 15:35:56 +04:00
var data = this . results . find ( ".select2-highlighted" ) . not ( ".select2-disabled" ) . closest ( '.select2-result' ) . not ( '.select2-result-unselectable' ) . data ( "select2-data" ) ;
2012-04-12 11:33:10 +04:00
if ( data ) {
this . onSelect ( data ) ;
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
getPlaceholder : function ( ) {
2012-05-03 07:19:00 +04:00
return this . opts . element . attr ( "placeholder" ) || this . opts . element . data ( "placeholder" ) || this . opts . placeholder ;
2012-04-12 11:33:10 +04:00
} ,
/ * *
* Get the desired width for the container element . This is
* derived first from option ` width ` passed to select2 , then
* the inline 'style' on the original element , and finally
* falls back to the jQuery calculated element width .
*
* @ returns The width string ( with units ) for the container .
* /
getContainerWidth : function ( ) {
2012-04-13 19:59:32 +04:00
var style , attrs , matches , i , l ;
2012-04-12 11:33:10 +04:00
if ( this . opts . width !== undefined )
return this . opts . width ;
2012-04-13 19:59:32 +04:00
style = this . opts . element . attr ( 'style' ) ;
2012-04-12 11:33:10 +04:00
if ( style !== undefined ) {
2012-04-13 19:59:32 +04:00
attrs = style . split ( ';' ) ;
for ( i = 0 , l = attrs . length ; i < l ; i = i + 1 ) {
matches = attrs [ i ] . replace ( /\s/g , '' )
2012-04-12 11:33:10 +04:00
. match ( /width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/ ) ;
2012-04-13 19:59:32 +04:00
if ( matches !== null && matches . length >= 1 )
2012-04-12 11:33:10 +04:00
return matches [ 1 ] ;
}
2012-03-31 21:20:49 +04:00
}
2012-04-12 11:33:10 +04:00
return this . opts . element . width ( ) + 'px' ;
2012-03-28 03:04:35 +04:00
}
2012-04-12 11:33:10 +04:00
} ) ;
2012-03-28 03:04:35 +04:00
2012-04-12 11:33:10 +04:00
SingleSelect2 = clazz ( AbstractSelect2 , {
createContainer : function ( ) {
return $ ( "<div></div>" , {
"class" : "select2-container" ,
"style" : "width: " + this . getContainerWidth ( )
} ) . html ( [
" <a href='javascript:void(0)' class='select2-choice'>" ,
" <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>" ,
" <div><b></b></div>" ,
"</a>" ,
" <div class='select2-drop' style='display:none;'>" ,
" <div class='select2-search'>" ,
" <input type='text' autocomplete='off'/>" ,
" </div>" ,
" <ul class='select2-results'>" ,
" </ul>" ,
"</div>" ] . join ( "" ) ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
open : function ( ) {
if ( this . opened ( ) ) return ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . parent . open . apply ( this , arguments ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
close : function ( ) {
if ( ! this . opened ( ) ) return ;
this . parent . close . apply ( this , arguments ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-21 10:59:44 +04:00
focus : function ( ) {
this . close ( ) ;
this . selection . focus ( ) ;
} ,
isFocused : function ( ) {
return this . selection . is ( ":focus" ) ;
} ,
2012-04-12 11:33:10 +04:00
cancel : function ( ) {
this . parent . cancel . apply ( this , arguments ) ;
this . selection . focus ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
initContainer : function ( ) {
2012-03-29 22:08:14 +04:00
2012-06-12 12:40:37 +04:00
var selection ,
container = this . container ,
dropdown = this . dropdown ,
2012-06-13 09:12:53 +04:00
containers = $ ( [ this . container . get ( 0 ) , this . dropdown . get ( 0 ) ] ) ,
2012-06-12 12:40:37 +04:00
clickingInside = false ,
2012-04-13 19:59:32 +04:00
selector = ".select2-choice" ;
2012-04-12 11:33:10 +04:00
this . selection = selection = container . find ( selector ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . search . bind ( "keydown" , this . bind ( function ( e ) {
switch ( e . which ) {
case KEY . UP :
case KEY . DOWN :
this . moveHighlight ( ( e . which === KEY . UP ) ? - 1 : 1 ) ;
killEvent ( e ) ;
return ;
case KEY . TAB :
case KEY . ENTER :
this . selectHighlighted ( ) ;
killEvent ( e ) ;
return ;
case KEY . ESC :
this . cancel ( e ) ;
2012-06-15 07:36:22 +04:00
killEvent ( e ) ;
2012-04-12 11:33:10 +04:00
return ;
}
} ) ) ;
2012-03-29 22:08:14 +04:00
2012-06-13 09:12:53 +04:00
containers . delegate ( selector , "click" , this . bind ( function ( e ) {
2012-04-12 11:33:10 +04:00
clickingInside = true ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( this . opened ( ) ) {
this . close ( ) ;
selection . focus ( ) ;
2012-06-07 08:52:08 +04:00
} else if ( this . enabled ) {
2012-04-12 11:33:10 +04:00
this . open ( ) ;
}
e . preventDefault ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
clickingInside = false ;
} ) ) ;
2012-06-13 09:12:53 +04:00
containers . delegate ( selector , "keydown" , this . bind ( function ( e ) {
2012-06-07 08:52:08 +04:00
if ( ! this . enabled || e . which === KEY . TAB || KEY . isControl ( e ) || KEY . isFunctionKey ( e ) || e . which === KEY . ESC ) {
2012-04-12 11:33:10 +04:00
return ;
}
this . open ( ) ;
if ( e . which === KEY . PAGE _UP || e . which === KEY . PAGE _DOWN || e . which === KEY . SPACE ) {
// prevent the page from scrolling
killEvent ( e ) ;
}
if ( e . which === KEY . ENTER ) {
// do not propagate the event otherwise we open, and propagate enter which closes
killEvent ( e ) ;
}
} ) ) ;
2012-06-14 23:16:44 +04:00
containers . delegate ( selector , "focus" , function ( ) { if ( this . enabled ) { containers . addClass ( "select2-container-active" ) ; dropdown . addClass ( "select2-drop-active" ) ; } } ) ;
2012-06-13 09:12:53 +04:00
containers . delegate ( selector , "blur" , this . bind ( function ( ) {
2012-04-12 11:33:10 +04:00
if ( clickingInside ) return ;
if ( ! this . opened ( ) ) this . blur ( ) ;
} ) ) ;
selection . delegate ( "abbr" , "click" , this . bind ( function ( e ) {
2012-06-07 08:52:08 +04:00
if ( ! this . enabled ) return ;
2012-04-12 11:33:10 +04:00
this . val ( "" ) ;
2012-03-29 22:08:14 +04:00
killEvent ( e ) ;
2012-04-12 11:33:10 +04:00
this . close ( ) ;
this . triggerChange ( ) ;
} ) ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . setPlaceholder ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
/ * *
* Sets selection based on source element ' s value
* /
initSelection : function ( ) {
var selected ;
if ( this . opts . element . val ( ) === "" ) {
this . updateSelection ( { id : "" , text : "" } ) ;
2012-03-29 22:08:14 +04:00
} else {
2012-04-12 11:33:10 +04:00
selected = this . opts . initSelection . call ( null , this . opts . element ) ;
if ( selected !== undefined && selected !== null ) {
this . updateSelection ( selected ) ;
}
2012-03-29 22:08:14 +04:00
}
2012-03-30 02:58:30 +04:00
this . close ( ) ;
2012-04-12 11:33:10 +04:00
this . setPlaceholder ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
prepareOpts : function ( ) {
var opts = this . parent . prepareOpts . apply ( this , arguments ) ;
2012-04-11 11:51:27 +04:00
2012-04-12 11:33:10 +04:00
if ( opts . element . get ( 0 ) . tagName . toLowerCase ( ) === "select" ) {
// install sthe selection initializer
2012-04-27 06:46:24 +04:00
opts . initSelection = function ( element ) {
2012-04-12 11:33:10 +04:00
var selected = element . find ( ":selected" ) ;
// a single select box always has a value, no need to null check 'selected'
return { id : selected . attr ( "value" ) , text : selected . text ( ) } ;
} ;
2012-04-10 10:50:21 +04:00
}
2012-04-12 11:33:10 +04:00
return opts ;
} ,
2012-04-10 10:50:21 +04:00
2012-04-12 11:33:10 +04:00
setPlaceholder : function ( ) {
var placeholder = this . getPlaceholder ( ) ;
2012-04-10 10:50:21 +04:00
2012-04-12 11:33:10 +04:00
if ( this . opts . element . val ( ) === "" && placeholder !== undefined ) {
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// check for a first blank option if attached to a select
if ( this . select && this . select . find ( "option:first" ) . text ( ) !== "" ) return ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( typeof ( placeholder ) === "object" ) {
this . updateSelection ( placeholder ) ;
} else {
this . selection . find ( "span" ) . html ( placeholder ) ;
}
this . selection . addClass ( "select2-default" ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . selection . find ( "abbr" ) . hide ( ) ;
2012-03-29 22:08:14 +04:00
}
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
postprocessResults : function ( data , initial ) {
2012-04-16 19:28:54 +04:00
var selected = 0 , self = this , showSearchInput = true ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// find the selected element in the result list
2012-03-29 22:08:14 +04:00
2012-06-16 23:00:09 +04:00
this . results . find ( ".select2-result" ) . each2 ( function ( i , elm ) {
if ( equal ( self . id ( elm . data ( "select2-data" ) ) , self . opts . element . val ( ) ) ) {
2012-04-12 11:33:10 +04:00
selected = i ;
return false ;
}
} ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// and highlight it
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . highlight ( selected ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// hide the search box if this is the first we got the results and there are a few of them
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( initial === true ) {
2012-06-08 01:14:46 +04:00
showSearchInput = this . showSearchInput = data . results . length >= this . opts . minimumResultsForSearch ;
2012-06-08 10:09:51 +04:00
this . container . find ( ".select2-search" ) [ showSearchInput ? "removeClass" : "addClass" ] ( "select2-search-hidden" ) ;
2012-04-16 17:23:14 +04:00
2012-04-21 10:59:44 +04:00
//add "select2-with-searchbox" to the container if search box is shown
this . container [ showSearchInput ? "addClass" : "removeClass" ] ( "select2-with-searchbox" ) ;
2012-04-12 11:33:10 +04:00
}
2012-03-31 01:06:34 +04:00
2012-04-12 11:33:10 +04:00
} ,
2012-03-31 01:06:34 +04:00
2012-04-12 11:33:10 +04:00
onSelect : function ( data ) {
var old = this . opts . element . val ( ) ;
2012-03-29 22:08:14 +04:00
2012-05-04 02:00:48 +04:00
this . opts . element . val ( this . id ( data ) ) ;
2012-04-12 11:33:10 +04:00
this . updateSelection ( data ) ;
this . close ( ) ;
this . selection . focus ( ) ;
2012-03-31 00:04:32 +04:00
2012-05-04 02:00:48 +04:00
if ( ! equal ( old , this . id ( data ) ) ) { this . triggerChange ( ) ; }
2012-04-12 11:33:10 +04:00
} ,
2012-03-31 00:04:32 +04:00
2012-04-12 11:33:10 +04:00
updateSelection : function ( data ) {
this . selection
. find ( "span" )
. html ( this . opts . formatSelection ( data ) ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . selection . removeClass ( "select2-default" ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( this . opts . allowClear && this . getPlaceholder ( ) !== undefined ) {
this . selection . find ( "abbr" ) . show ( ) ;
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
val : function ( ) {
var val , data = null ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( arguments . length === 0 ) {
return this . opts . element . val ( ) ;
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
val = arguments [ 0 ] ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( this . select ) {
// val is an id
this . select
. val ( val )
2012-06-16 23:00:09 +04:00
. find ( ":selected" ) . each2 ( function ( i , elm ) {
data = { id : elm . attr ( "value" ) , text : elm . text ( ) } ;
2012-04-12 11:33:10 +04:00
return false ;
} ) ;
this . updateSelection ( data ) ;
} else {
2012-04-17 19:05:14 +04:00
// val is an object. !val is true for [undefined,null,'']
2012-05-04 02:00:48 +04:00
this . opts . element . val ( ! val ? "" : this . id ( val ) ) ;
2012-04-12 11:33:10 +04:00
this . updateSelection ( val ) ;
}
this . setPlaceholder ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
clearSearch : function ( ) {
this . search . val ( "" ) ;
}
} ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
MultiSelect2 = clazz ( AbstractSelect2 , {
createContainer : function ( ) {
return $ ( "<div></div>" , {
"class" : "select2-container select2-container-multi" ,
"style" : "width: " + this . getContainerWidth ( )
} ) . html ( [
" <ul class='select2-choices'>" ,
//"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" ,
" <li class='select2-search-field'>" ,
" <input type='text' autocomplete='off' style='width: 25px;'>" ,
" </li>" ,
"</ul>" ,
2012-06-12 12:49:48 +04:00
"<div class='select2-drop select2-drop-multi' style='display:none;'>" ,
2012-04-12 11:33:10 +04:00
" <ul class='select2-results'>" ,
" </ul>" ,
"</div>" ] . join ( "" ) ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
prepareOpts : function ( ) {
var opts = this . parent . prepareOpts . apply ( this , arguments ) ;
2012-04-24 21:46:21 +04:00
opts = $ . extend ( { } , {
closeOnSelect : true
} , opts ) ;
2012-04-17 19:04:00 +04:00
// TODO validate placeholder is a string if specified
2012-04-12 11:33:10 +04:00
if ( opts . element . get ( 0 ) . tagName . toLowerCase ( ) === "select" ) {
// install sthe selection initializer
2012-04-27 06:46:24 +04:00
opts . initSelection = function ( element ) {
2012-04-12 11:33:10 +04:00
var data = [ ] ;
2012-06-16 23:00:09 +04:00
element . find ( ":selected" ) . each2 ( function ( i , elm ) {
data . push ( { id : elm . attr ( "value" ) , text : elm . text ( ) } ) ;
2012-04-12 11:33:10 +04:00
} ) ;
return data ;
} ;
}
2012-04-10 10:50:21 +04:00
2012-04-12 11:33:10 +04:00
return opts ;
} ,
2012-04-10 10:50:21 +04:00
2012-04-12 11:33:10 +04:00
initContainer : function ( ) {
2012-04-10 10:50:21 +04:00
2012-04-13 19:59:32 +04:00
var selector = ".select2-choices" , selection ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . searchContainer = this . container . find ( ".select2-search-field" ) ;
this . selection = selection = this . container . find ( selector ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . search . bind ( "keydown" , this . bind ( function ( e ) {
2012-06-07 08:52:08 +04:00
if ( ! this . enabled ) return ;
2012-04-12 11:33:10 +04:00
if ( e . which === KEY . BACKSPACE && this . search . val ( ) === "" ) {
this . close ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
var choices ,
2012-04-13 19:59:32 +04:00
selected = selection . find ( ".select2-search-choice-focus" ) ;
2012-04-12 11:33:10 +04:00
if ( selected . length > 0 ) {
this . unselect ( selected . first ( ) ) ;
this . search . width ( 10 ) ;
killEvent ( e ) ;
return ;
}
2012-03-29 22:08:14 +04:00
2012-04-13 19:59:32 +04:00
choices = selection . find ( ".select2-search-choice" ) ;
2012-04-12 11:33:10 +04:00
if ( choices . length > 0 ) {
choices . last ( ) . addClass ( "select2-search-choice-focus" ) ;
}
} else {
2012-04-13 19:59:32 +04:00
selection . find ( ".select2-search-choice-focus" ) . removeClass ( "select2-search-choice-focus" ) ;
2012-03-29 22:08:14 +04:00
}
2012-04-12 11:33:10 +04:00
if ( this . opened ( ) ) {
switch ( e . which ) {
case KEY . UP :
case KEY . DOWN :
this . moveHighlight ( ( e . which === KEY . UP ) ? - 1 : 1 ) ;
killEvent ( e ) ;
return ;
case KEY . ENTER :
2012-05-22 17:30:10 +04:00
case KEY . TAB :
2012-04-12 11:33:10 +04:00
this . selectHighlighted ( ) ;
killEvent ( e ) ;
return ;
case KEY . ESC :
this . cancel ( e ) ;
2012-06-15 07:36:22 +04:00
killEvent ( e ) ;
2012-04-12 11:33:10 +04:00
return ;
}
2012-03-29 22:08:14 +04:00
}
2012-04-12 11:33:10 +04:00
if ( e . which === KEY . TAB || KEY . isControl ( e ) || KEY . isFunctionKey ( e ) || e . which === KEY . BACKSPACE || e . which === KEY . ESC ) {
2012-03-29 22:08:14 +04:00
return ;
}
2012-04-12 11:33:10 +04:00
this . open ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( e . which === KEY . PAGE _UP || e . which === KEY . PAGE _DOWN ) {
// prevent the page from scrolling
killEvent ( e ) ;
}
} ) ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . search . bind ( "keyup" , this . bind ( this . resizeSearch ) ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . container . delegate ( selector , "click" , this . bind ( function ( e ) {
2012-06-07 08:52:08 +04:00
if ( ! this . enabled ) return ;
2012-04-12 11:33:10 +04:00
this . open ( ) ;
this . focusSearch ( ) ;
e . preventDefault ( ) ;
} ) ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . container . delegate ( selector , "focus" , this . bind ( function ( ) {
2012-06-07 08:52:08 +04:00
if ( ! this . enabled ) return ;
2012-04-12 11:33:10 +04:00
this . container . addClass ( "select2-container-active" ) ;
2012-06-12 12:40:37 +04:00
this . dropdown . addClass ( "select2-drop-active" ) ;
2012-04-12 11:33:10 +04:00
this . clearPlaceholder ( ) ;
} ) ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// set the placeholder if necessary
this . clearSearch ( ) ;
} ,
2012-04-11 11:51:27 +04:00
2012-06-07 08:52:08 +04:00
enable : function ( ) {
if ( this . enabled ) return ;
this . parent . enable . apply ( this , arguments ) ;
this . search . show ( ) ;
} ,
disable : function ( ) {
if ( ! this . enabled ) return ;
this . parent . disable . apply ( this , arguments ) ;
this . search . hide ( ) ;
} ,
2012-04-12 11:33:10 +04:00
initSelection : function ( ) {
var data ;
if ( this . opts . element . val ( ) === "" ) {
this . updateSelection ( [ ] ) ;
}
if ( this . select || this . opts . element . val ( ) !== "" ) {
data = this . opts . initSelection . call ( null , this . opts . element ) ;
2012-04-13 19:59:32 +04:00
if ( data !== undefined && data !== null ) {
2012-04-12 11:33:10 +04:00
this . updateSelection ( data ) ;
}
2012-04-10 10:50:21 +04:00
}
2012-04-11 11:51:27 +04:00
2012-04-12 11:33:10 +04:00
this . close ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
// set the placeholder if necessary
this . clearSearch ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
clearSearch : function ( ) {
var placeholder = this . getPlaceholder ( ) ;
2012-03-29 22:08:14 +04:00
2012-06-14 23:16:44 +04:00
if ( placeholder !== undefined && this . getVal ( ) . length === 0 && this . search . hasClass ( "select2-focused" ) === false ) {
2012-05-03 09:48:44 +04:00
2012-04-12 11:33:10 +04:00
this . search . val ( placeholder ) . addClass ( "select2-default" ) ;
2012-05-02 20:26:00 +04:00
// stretch the search box to full width of the container so as much of the placeholder is visible as possible
this . search . width ( this . getContainerWidth ( ) ) ;
} else {
this . search . val ( "" ) . width ( 10 ) ;
2012-04-12 11:33:10 +04:00
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
clearPlaceholder : function ( ) {
if ( this . search . hasClass ( "select2-default" ) ) {
this . search . val ( "" ) . removeClass ( "select2-default" ) ;
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
open : function ( ) {
if ( this . opened ( ) ) return ;
this . parent . open . apply ( this , arguments ) ;
this . resizeSearch ( ) ;
this . focusSearch ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
close : function ( ) {
if ( ! this . opened ( ) ) return ;
this . parent . close . apply ( this , arguments ) ;
} ,
2012-04-11 11:51:27 +04:00
2012-04-21 10:59:44 +04:00
focus : function ( ) {
this . close ( ) ;
this . search . focus ( ) ;
} ,
isFocused : function ( ) {
2012-05-03 09:52:05 +04:00
return this . search . hasClass ( "select2-focused" ) ;
2012-04-21 10:59:44 +04:00
} ,
2012-04-12 11:33:10 +04:00
updateSelection : function ( data ) {
var ids = [ ] , filtered = [ ] , self = this ;
2012-04-11 11:51:27 +04:00
2012-04-12 11:33:10 +04:00
// filter out duplicates
$ ( data ) . each ( function ( ) {
2012-05-04 02:00:48 +04:00
if ( indexOf ( self . id ( this ) , ids ) < 0 ) {
ids . push ( self . id ( this ) ) ;
2012-04-12 11:33:10 +04:00
filtered . push ( this ) ;
}
} ) ;
data = filtered ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . selection . find ( ".select2-search-choice" ) . remove ( ) ;
$ ( data ) . each ( function ( ) {
self . addSelectedChoice ( this ) ;
} ) ;
self . postprocessResults ( ) ;
} ,
2012-03-31 01:06:34 +04:00
2012-04-12 11:33:10 +04:00
onSelect : function ( data ) {
this . addSelectedChoice ( data ) ;
if ( this . select ) { this . postprocessResults ( ) ; }
2012-04-24 21:46:21 +04:00
if ( this . opts . closeOnSelect ) {
this . close ( ) ;
this . search . width ( 10 ) ;
} else {
this . search . width ( 10 ) ;
this . resizeSearch ( ) ;
}
2012-03-31 01:06:34 +04:00
2012-04-12 11:33:10 +04:00
// since its not possible to select an element that has already been
// added we do not need to check if this is a new element before firing change
this . triggerChange ( ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
this . focusSearch ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
cancel : function ( ) {
2012-03-29 22:08:14 +04:00
this . close ( ) ;
this . focusSearch ( ) ;
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
addSelectedChoice : function ( data ) {
var choice ,
2012-05-04 02:00:48 +04:00
id = this . id ( data ) ,
2012-04-12 11:33:10 +04:00
parts ,
val = this . getVal ( ) ;
parts = [ "<li class='select2-search-choice'>" ,
this . opts . formatSelection ( data ) ,
"<a href='javascript:void(0)' class='select2-search-choice-close' tabindex='-1'></a>" ,
"</li>"
] ;
choice = $ ( parts . join ( "" ) ) ;
choice . find ( "a" )
. bind ( "click dblclick" , this . bind ( function ( e ) {
2012-06-07 08:52:08 +04:00
if ( ! this . enabled ) return ;
2012-04-12 11:33:10 +04:00
this . unselect ( $ ( e . target ) ) ;
this . selection . find ( ".select2-search-choice-focus" ) . removeClass ( "select2-search-choice-focus" ) ;
killEvent ( e ) ;
this . close ( ) ;
this . focusSearch ( ) ;
} ) ) . bind ( "focus" , this . bind ( function ( ) {
2012-06-07 08:52:08 +04:00
if ( ! this . enabled ) return ;
2012-04-12 11:33:10 +04:00
this . container . addClass ( "select2-container-active" ) ;
2012-06-12 12:40:37 +04:00
this . dropdown . addClass ( "select2-drop-active" ) ;
2012-04-12 11:33:10 +04:00
} ) ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
choice . data ( "select2-data" , data ) ;
choice . insertBefore ( this . searchContainer ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
val . push ( id ) ;
this . setVal ( val ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
unselect : function ( selected ) {
var val = this . getVal ( ) ,
index ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
selected = selected . closest ( ".select2-search-choice" ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( selected . length === 0 ) {
throw "Invalid argument: " + selected + ". Must be .select2-search-choice" ;
}
2012-03-29 22:08:14 +04:00
2012-05-04 02:05:39 +04:00
index = indexOf ( this . id ( selected . data ( "select2-data" ) ) , val ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( index >= 0 ) {
val . splice ( index , 1 ) ;
this . setVal ( val ) ;
if ( this . select ) this . postprocessResults ( ) ;
2012-03-29 22:08:14 +04:00
}
2012-04-12 11:33:10 +04:00
selected . remove ( ) ;
this . triggerChange ( ) ;
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
postprocessResults : function ( ) {
var val = this . getVal ( ) ,
choices = this . results . find ( ".select2-result" ) ,
self = this ;
2012-03-29 22:08:14 +04:00
2012-06-16 23:00:09 +04:00
choices . each2 ( function ( i , choice ) {
var id = self . id ( choice . data ( "select2-data" ) ) ;
2012-04-12 11:33:10 +04:00
if ( indexOf ( id , val ) >= 0 ) {
choice . addClass ( "select2-disabled" ) ;
} else {
choice . removeClass ( "select2-disabled" ) ;
}
} ) ;
2012-03-29 22:08:14 +04:00
2012-06-16 23:00:09 +04:00
choices . each2 ( function ( i , choice ) {
if ( ! choice . hasClass ( "select2-disabled" ) ) {
2012-04-12 11:33:10 +04:00
self . highlight ( i ) ;
return false ;
}
} ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
resizeSearch : function ( ) {
2012-03-29 22:08:14 +04:00
2012-06-17 16:19:27 +04:00
var minimumWidth , left , maxWidth , containerLeft , searchWidth ,
sideBorderPadding = getSideBorderPadding ( this . search ) ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
minimumWidth = measureTextWidth ( this . search ) + 10 ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
left = this . search . offset ( ) . left ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
maxWidth = this . selection . width ( ) ;
containerLeft = this . selection . offset ( ) . left ;
2012-03-29 22:08:14 +04:00
2012-06-17 16:19:27 +04:00
searchWidth = maxWidth - ( left - containerLeft ) - sideBorderPadding ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( searchWidth < minimumWidth ) {
2012-06-17 16:19:27 +04:00
searchWidth = maxWidth - sideBorderPadding ;
2012-04-12 11:33:10 +04:00
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( searchWidth < 40 ) {
2012-06-17 16:19:27 +04:00
searchWidth = maxWidth - sideBorderPadding ;
2012-04-12 11:33:10 +04:00
}
this . search . width ( searchWidth ) ;
} ,
2012-04-11 11:51:27 +04:00
2012-04-12 11:33:10 +04:00
getVal : function ( ) {
2012-04-13 19:59:32 +04:00
var val ;
2012-04-12 11:33:10 +04:00
if ( this . select ) {
val = this . select . val ( ) ;
return val === null ? [ ] : val ;
} else {
val = this . opts . element . val ( ) ;
2012-04-13 19:07:59 +04:00
return splitVal ( val , "," ) ;
2012-04-12 11:33:10 +04:00
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
setVal : function ( val ) {
2012-05-30 04:04:52 +04:00
var unique ;
2012-04-12 11:33:10 +04:00
if ( this . select ) {
this . select . val ( val ) ;
} else {
2012-05-30 04:04:52 +04:00
unique = [ ] ;
2012-04-12 11:33:10 +04:00
// filter out duplicates
$ ( val ) . each ( function ( ) {
if ( indexOf ( this , unique ) < 0 ) unique . push ( this ) ;
} ) ;
this . opts . element . val ( unique . length === 0 ? "" : unique . join ( "," ) ) ;
}
} ,
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
val : function ( ) {
2012-05-04 02:00:48 +04:00
var val , data = [ ] , self = this ;
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
if ( arguments . length === 0 ) {
return this . getVal ( ) ;
}
2012-03-29 22:08:14 +04:00
2012-04-12 11:33:10 +04:00
val = arguments [ 0 ] ;
if ( this . select ) {
// val is a list of ids
this . setVal ( val ) ;
this . select . find ( ":selected" ) . each ( function ( ) {
data . push ( { id : $ ( this ) . attr ( "value" ) , text : $ ( this ) . text ( ) } ) ;
} ) ;
this . updateSelection ( data ) ;
} else {
val = ( val === null ) ? [ ] : val ;
this . setVal ( val ) ;
// val is a list of objects
2012-05-04 02:00:48 +04:00
$ ( val ) . each ( function ( ) { data . push ( self . id ( this ) ) ; } ) ;
2012-04-12 11:33:10 +04:00
this . setVal ( data ) ;
this . updateSelection ( val ) ;
}
2012-04-27 07:04:57 +04:00
this . clearSearch ( ) ;
2012-05-30 04:04:52 +04:00
} ,
onSortStart : function ( ) {
if ( this . select ) {
throw new Error ( "Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead." ) ;
}
// collapse search field into 0 width so its container can be collapsed as well
this . search . width ( 0 ) ;
// hide the container
this . searchContainer . hide ( ) ;
} ,
onSortEnd : function ( ) {
var val = [ ] , self = this ;
// show search and move it to the end of the list
this . searchContainer . show ( ) ;
// make sure the search container is the last item in the list
this . searchContainer . appendTo ( this . searchContainer . parent ( ) ) ;
// since we collapsed the width in dragStarteed, we resize it here
this . resizeSearch ( ) ;
// update selection
this . selection . find ( ".select2-search-choice" ) . each ( function ( ) {
val . push ( self . opts . id ( $ ( this ) . data ( "select2-data" ) ) ) ;
} ) ;
this . setVal ( val ) ;
this . triggerChange ( ) ;
2012-03-29 22:08:14 +04:00
}
2012-04-12 11:33:10 +04:00
} ) ;
2012-03-29 22:08:14 +04:00
$ . fn . select2 = function ( ) {
var args = Array . prototype . slice . call ( arguments , 0 ) ,
opts ,
select2 ,
2012-06-13 08:59:39 +04:00
value , multiple , allowedMethods = [ "val" , "destroy" , "open" , "close" , "focus" , "isFocused" , "container" , "onSortStart" , "onSortEnd" , "enable" , "disable" , "positionDropdown" ] ;
2012-03-29 22:08:14 +04:00
this . each ( function ( ) {
if ( args . length === 0 || typeof ( args [ 0 ] ) === "object" ) {
2012-04-28 09:39:40 +04:00
opts = args . length === 0 ? { } : $ . extend ( { } , args [ 0 ] ) ;
2012-03-29 22:08:14 +04:00
opts . element = $ ( this ) ;
if ( opts . element . get ( 0 ) . tagName . toLowerCase ( ) === "select" ) {
2012-03-30 02:58:30 +04:00
multiple = opts . element . attr ( "multiple" ) ;
2012-03-29 22:08:14 +04:00
} else {
multiple = opts . multiple || false ;
2012-04-03 09:27:58 +04:00
if ( "tags" in opts ) { opts . multiple = multiple = true ; }
2012-03-29 22:08:14 +04:00
}
select2 = multiple ? new MultiSelect2 ( ) : new SingleSelect2 ( ) ;
select2 . init ( opts ) ;
} else if ( typeof ( args [ 0 ] ) === "string" ) {
if ( indexOf ( args [ 0 ] , allowedMethods ) < 0 ) {
throw "Unknown method: " + args [ 0 ] ;
}
value = undefined ;
select2 = $ ( this ) . data ( "select2" ) ;
2012-04-12 10:28:54 +04:00
if ( select2 === undefined ) return ;
2012-05-29 10:15:41 +04:00
if ( args [ 0 ] === "container" ) {
value = select2 . container ;
} else {
value = select2 [ args [ 0 ] ] . apply ( select2 , args . slice ( 1 ) ) ;
}
2012-03-29 22:08:14 +04:00
if ( value !== undefined ) { return false ; }
} else {
throw "Invalid arguments to select2 plugin: " + args ;
}
} ) ;
return ( value === undefined ) ? this : value ;
} ;
2012-04-10 20:46:20 +04:00
// exports
2012-04-02 19:36:55 +04:00
window . Select2 = {
query : {
ajax : ajax ,
2012-04-10 20:12:19 +04:00
local : local ,
tags : tags
2012-04-02 19:36:55 +04:00
} , util : {
debounce : debounce
} , "class" : {
2012-04-13 19:59:32 +04:00
"abstract" : AbstractSelect2 ,
"single" : SingleSelect2 ,
"multi" : MultiSelect2
2012-04-02 19:36:55 +04:00
}
} ;
2012-04-10 20:46:20 +04:00
} ( jQuery ) ) ;