11/*
22 * jQuery Plugin: Tokenizing Autocomplete Text Entry
3- * Version 1.5 .0
3+ * Version 1.6 .0
44 *
55 * Copyright (c) 2009 James Smith (http://loopj.com)
66 * Licensed jointly under the GPL and MIT licenses,
1111( function ( $ ) {
1212// Default settings
1313var DEFAULT_SETTINGS = {
14+ // Search settings
15+ method : "GET" ,
16+ contentType : "json" ,
17+ queryParam : "q" ,
18+ searchDelay : 300 ,
19+ minChars : 1 ,
20+ propertyToSearch : "name" ,
21+ jsonContainer : null ,
22+
23+ // Display settings
1424 hintText : "Type in a search term" ,
1525 noResultsText : "No results" ,
1626 searchingText : "Searching..." ,
1727 deleteText : "×" ,
18- searchDelay : 300 ,
19- minChars : 1 ,
28+ animateDropdown : true ,
29+
30+ // Tokenization settings
2031 tokenLimit : null ,
21- jsonContainer : null ,
22- method : "GET" ,
23- contentType : "json" ,
24- queryParam : "q" ,
2532 tokenDelimiter : "," ,
2633 preventDuplicates : false ,
34+
35+ // Output settings
36+ tokenValue : "id" ,
37+
38+ // Prepopulation settings
2739 prePopulate : null ,
2840 processPrePopulate : false ,
29- animateDropdown : true ,
41+
42+ // Manipulation settings
43+ idPrefix : "token-input-" ,
44+
45+ // Formatters
46+ resultsFormatter : function ( item ) { return "<li>" + item [ this . propertyToSearch ] + "</li>" } ,
47+ tokenFormatter : function ( item ) { return "<li><p>" + item [ this . propertyToSearch ] + "</p></li>" } ,
48+
49+ // Callbacks
3050 onResult : null ,
3151 onAdd : null ,
3252 onDelete : null ,
33- idPrefix : "token-input-"
53+ onReady : null
3454} ;
3555
3656// Default classes to use when theming
@@ -93,7 +113,10 @@ var methods = {
93113 remove : function ( item ) {
94114 this . data ( "tokenInputObject" ) . remove ( item ) ;
95115 return this ;
96- }
116+ } ,
117+ get : function ( ) {
118+ return this . data ( "tokenInputObject" ) . getTokens ( ) ;
119+ }
97120}
98121
99122// Expose the .tokenInput function to jQuery as a plugin
@@ -113,16 +136,19 @@ $.TokenList = function (input, url_or_data, settings) {
113136 //
114137
115138 // Configure the data source
116- if ( typeof ( url_or_data ) === "string" ) {
139+ if ( $ . type ( url_or_data ) === "string" || $ . type ( url_or_data ) === "function ") {
117140 // Set the url to query against
118141 settings . url = url_or_data ;
119142
143+ // If the URL is a function, evaluate it here to do our initalization work
144+ var url = computeURL ( ) ;
145+
120146 // Make a smart guess about cross-domain if it wasn't explicitly specified
121147 if ( settings . crossDomain === undefined ) {
122- if ( settings . url . indexOf ( "://" ) === - 1 ) {
148+ if ( url . indexOf ( "://" ) === - 1 ) {
123149 settings . crossDomain = false ;
124150 } else {
125- settings . crossDomain = ( location . href . split ( / \/ + / g) [ 1 ] !== settings . url . split ( / \/ + / g) [ 1 ] ) ;
151+ settings . crossDomain = ( location . href . split ( / \/ + / g) [ 1 ] !== url . split ( / \/ + / g) [ 1 ] ) ;
126152 }
127153 }
128154 } else if ( typeof ( url_or_data ) === "object" ) {
@@ -223,6 +249,7 @@ $.TokenList = function (input, url_or_data, settings) {
223249 if ( ! $ ( this ) . val ( ) . length ) {
224250 if ( selected_token ) {
225251 delete_token ( $ ( selected_token ) ) ;
252+ hidden_input . change ( ) ;
226253 } else if ( previous_token . length ) {
227254 select_token ( $ ( previous_token . get ( 0 ) ) ) ;
228255 }
@@ -242,6 +269,7 @@ $.TokenList = function (input, url_or_data, settings) {
242269 case KEY . COMMA :
243270 if ( selected_dropdown_item ) {
244271 add_token ( $ ( selected_dropdown_item ) . data ( "tokeninput" ) ) ;
272+ hidden_input . change ( ) ;
245273 return false ;
246274 }
247275 break ;
@@ -346,6 +374,10 @@ $.TokenList = function (input, url_or_data, settings) {
346374 } ) ;
347375 }
348376
377+ // Initialization is done
378+ if ( $ . isFunction ( settings . onReady ) ) {
379+ settings . onReady . call ( ) ;
380+ }
349381
350382 //
351383 // Public functions
@@ -380,6 +412,10 @@ $.TokenList = function (input, url_or_data, settings) {
380412 }
381413 } ) ;
382414 }
415+
416+ this . getTokens = function ( ) {
417+ return saved_tokens ;
418+ }
383419
384420 //
385421 // Private functions
@@ -390,8 +426,6 @@ $.TokenList = function (input, url_or_data, settings) {
390426 input_box . hide ( ) ;
391427 hide_dropdown ( ) ;
392428 return ;
393- } else {
394- input_box . focus ( ) ;
395429 }
396430 }
397431
@@ -413,7 +447,8 @@ $.TokenList = function (input, url_or_data, settings) {
413447
414448 // Inner function to a token to the list
415449 function insert_token ( item ) {
416- var this_token = $ ( "<li><p>" + item . name + "</p></li>" )
450+ var this_token = settings . tokenFormatter ( item ) ;
451+ this_token = $ ( this_token )
417452 . addClass ( settings . classes . token )
418453 . insertBefore ( input_token ) ;
419454
@@ -423,25 +458,30 @@ $.TokenList = function (input, url_or_data, settings) {
423458 . appendTo ( this_token )
424459 . click ( function ( ) {
425460 delete_token ( $ ( this ) . parent ( ) ) ;
461+ hidden_input . change ( ) ;
426462 return false ;
427463 } ) ;
428464
429465 // Store data on the token
430- var token_data = { "id" : item . id , "name" : item . name } ;
466+ var token_data = { "id" : item . id } ;
467+ token_data [ settings . propertyToSearch ] = item [ settings . propertyToSearch ] ;
431468 $ . data ( this_token . get ( 0 ) , "tokeninput" , item ) ;
432469
433470 // Save this token for duplicate checking
434471 saved_tokens = saved_tokens . slice ( 0 , selected_token_index ) . concat ( [ token_data ] ) . concat ( saved_tokens . slice ( selected_token_index ) ) ;
435472 selected_token_index ++ ;
436473
437474 // Update the hidden input
438- var token_ids = $ . map ( saved_tokens , function ( el ) {
439- return el . id ;
440- } ) ;
441- hidden_input . val ( token_ids . join ( settings . tokenDelimiter ) ) ;
475+ update_hidden_input ( saved_tokens , hidden_input ) ;
442476
443477 token_count += 1 ;
444478
479+ // Check the token limit
480+ if ( settings . tokenLimit !== null && token_count >= settings . tokenLimit ) {
481+ input_box . hide ( ) ;
482+ hide_dropdown ( ) ;
483+ }
484+
445485 return this_token ;
446486 }
447487
@@ -470,8 +510,10 @@ $.TokenList = function (input, url_or_data, settings) {
470510 }
471511
472512 // Insert the new tokens
473- insert_token ( item ) ;
474- checkTokenLimit ( ) ;
513+ if ( settings . tokenLimit == null || token_count < settings . tokenLimit ) {
514+ insert_token ( item ) ;
515+ checkTokenLimit ( ) ;
516+ }
475517
476518 // Clear input box
477519 input_box . val ( "" ) ;
@@ -553,10 +595,7 @@ $.TokenList = function (input, url_or_data, settings) {
553595 if ( index < selected_token_index ) selected_token_index -- ;
554596
555597 // Update the hidden input
556- var token_ids = $ . map ( saved_tokens , function ( el ) {
557- return el . id ;
558- } ) ;
559- hidden_input . val ( token_ids . join ( settings . tokenDelimiter ) ) ;
598+ update_hidden_input ( saved_tokens , hidden_input ) ;
560599
561600 token_count -= 1 ;
562601
@@ -573,6 +612,15 @@ $.TokenList = function (input, url_or_data, settings) {
573612 }
574613 }
575614
615+ // Update the hidden input box value
616+ function update_hidden_input ( saved_tokens , hidden_input ) {
617+ var token_values = $ . map ( saved_tokens , function ( el ) {
618+ return el [ settings . tokenValue ] ;
619+ } ) ;
620+ hidden_input . val ( token_values . join ( settings . tokenDelimiter ) ) ;
621+
622+ }
623+
576624 // Hide and clear the results dropdown
577625 function hide_dropdown ( ) {
578626 dropdown . hide ( ) . empty ( ) ;
@@ -608,6 +656,10 @@ $.TokenList = function (input, url_or_data, settings) {
608656 function highlight_term ( value , term ) {
609657 return value . replace ( new RegExp ( "(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)" , "gi" ) , "<b>$1</b>" ) ;
610658 }
659+
660+ function find_value_and_highlight_term ( template , value , term ) {
661+ return template . replace ( new RegExp ( "(?![^&;]+;)(?!<[^<>]*)(" + value + ")(?![^<>]*>)(?![^&;]+;)" , "g" ) , highlight_term ( value , term ) ) ;
662+ }
611663
612664 // Populate the results dropdown with some results
613665 function populate_dropdown ( query , results ) {
@@ -620,14 +672,18 @@ $.TokenList = function (input, url_or_data, settings) {
620672 } )
621673 . mousedown ( function ( event ) {
622674 add_token ( $ ( event . target ) . closest ( "li" ) . data ( "tokeninput" ) ) ;
675+ hidden_input . change ( ) ;
623676 return false ;
624677 } )
625678 . hide ( ) ;
626679
627680 $ . each ( results , function ( index , value ) {
628- var this_li = $ ( "<li>" + highlight_term ( value . name , query ) + "</li>" )
629- . appendTo ( dropdown_ul ) ;
630-
681+ var this_li = settings . resultsFormatter ( value ) ;
682+
683+ this_li = find_value_and_highlight_term ( this_li , value [ settings . propertyToSearch ] , query ) ;
684+
685+ this_li = $ ( this_li ) . appendTo ( dropdown_ul ) ;
686+
631687 if ( index % 2 ) {
632688 this_li . addClass ( settings . classes . dropdownItem ) ;
633689 } else {
@@ -699,17 +755,19 @@ $.TokenList = function (input, url_or_data, settings) {
699755
700756 // Do the actual search
701757 function run_search ( query ) {
702- var cached_results = cache . get ( query ) ;
758+ var cache_key = query + computeURL ( ) ;
759+ var cached_results = cache . get ( cache_key ) ;
703760 if ( cached_results ) {
704761 populate_dropdown ( query , cached_results ) ;
705762 } else {
706763 // Are we doing an ajax search or local data search?
707764 if ( settings . url ) {
765+ var url = computeURL ( ) ;
708766 // Extract exisiting get params
709767 var ajax_params = { } ;
710768 ajax_params . data = { } ;
711- if ( settings . url . indexOf ( "?" ) > - 1 ) {
712- var parts = settings . url . split ( "?" ) ;
769+ if ( url . indexOf ( "?" ) > - 1 ) {
770+ var parts = url . split ( "?" ) ;
713771 ajax_params . url = parts [ 0 ] ;
714772
715773 var param_array = parts [ 1 ] . split ( "&" ) ;
@@ -718,7 +776,7 @@ $.TokenList = function (input, url_or_data, settings) {
718776 ajax_params . data [ kv [ 0 ] ] = kv [ 1 ] ;
719777 } ) ;
720778 } else {
721- ajax_params . url = settings . url ;
779+ ajax_params . url = url ;
722780 }
723781
724782 // Prepare the request
@@ -734,7 +792,7 @@ $.TokenList = function (input, url_or_data, settings) {
734792 if ( $ . isFunction ( settings . onResult ) ) {
735793 results = settings . onResult . call ( hidden_input , results ) ;
736794 }
737- cache . add ( query , settings . jsonContainer ? results [ settings . jsonContainer ] : results ) ;
795+ cache . add ( cache_key , settings . jsonContainer ? results [ settings . jsonContainer ] : results ) ;
738796
739797 // only populate the dropdown if the results are associated with the active search query
740798 if ( input_box . val ( ) . toLowerCase ( ) === query ) {
@@ -747,17 +805,26 @@ $.TokenList = function (input, url_or_data, settings) {
747805 } else if ( settings . local_data ) {
748806 // Do the search through local data
749807 var results = $ . grep ( settings . local_data , function ( row ) {
750- return row . name . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) > - 1 ;
808+ return row [ settings . propertyToSearch ] . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) > - 1 ;
751809 } ) ;
752810
753811 if ( $ . isFunction ( settings . onResult ) ) {
754812 results = settings . onResult . call ( hidden_input , results ) ;
755813 }
756- cache . add ( query , results ) ;
814+ cache . add ( cache_key , results ) ;
757815 populate_dropdown ( query , results ) ;
758816 }
759817 }
760818 }
819+
820+ // compute the dynamic URL
821+ function computeURL ( ) {
822+ var url = settings . url ;
823+ if ( typeof settings . url == 'function' ) {
824+ url = settings . url . call ( ) ;
825+ }
826+ return url ;
827+ }
761828} ;
762829
763830// Really basic cache for the results
0 commit comments