1 | /** |
---|
2 | DON'T MAKE AUTOMATIC UPGRADE |
---|
3 | This is a merged version of : |
---|
4 | + https://github.com/gr2m/jquery-tokeninput/ |
---|
5 | + https://github.com/mistic100/jquery-tokeninput/ |
---|
6 | */ |
---|
7 | |
---|
8 | /* |
---|
9 | * jQuery Plugin: Tokenizing Autocomplete Text Entry |
---|
10 | * Version 1.4.2 |
---|
11 | * |
---|
12 | * Copyright (c) 2009 James Smith (http://loopj.com) |
---|
13 | * Licensed jointly under the GPL and MIT licenses, |
---|
14 | * choose which one suits your project best! |
---|
15 | * |
---|
16 | */ |
---|
17 | |
---|
18 | (function ($) { |
---|
19 | // Default settings |
---|
20 | var DEFAULT_SETTINGS = { |
---|
21 | hintText: "Type in a search term", |
---|
22 | noResultsText: "No results", |
---|
23 | searchingText: "Searching...", |
---|
24 | newText: "(new)", |
---|
25 | deleteText: "×", |
---|
26 | searchDelay: 300, |
---|
27 | minChars: 1, |
---|
28 | tokenLimit: null, |
---|
29 | jsonContainer: null, |
---|
30 | method: "GET", |
---|
31 | contentType: "json", |
---|
32 | queryParam: "q", |
---|
33 | tokenDelimiter: ",", |
---|
34 | preventDuplicates: false, |
---|
35 | prePopulate: null, |
---|
36 | processPrePopulate: false, |
---|
37 | animateDropdown: true, |
---|
38 | onResult: null, |
---|
39 | onAdd: null, |
---|
40 | onDelete: null, |
---|
41 | allowCreation: false |
---|
42 | }; |
---|
43 | |
---|
44 | // Default classes to use when theming |
---|
45 | var DEFAULT_CLASSES = { |
---|
46 | tokenList: "token-input-list", |
---|
47 | token: "token-input-token", |
---|
48 | tokenDelete: "token-input-delete-token", |
---|
49 | selectedToken: "token-input-selected-token", |
---|
50 | highlightedToken: "token-input-highlighted-token", |
---|
51 | dropdown: "token-input-dropdown", |
---|
52 | dropdownItem: "token-input-dropdown-item", |
---|
53 | dropdownItem2: "token-input-dropdown-item2", |
---|
54 | selectedDropdownItem: "token-input-selected-dropdown-item", |
---|
55 | inputToken: "token-input-input-token" |
---|
56 | }; |
---|
57 | |
---|
58 | // Input box position "enum" |
---|
59 | var POSITION = { |
---|
60 | BEFORE: 0, |
---|
61 | AFTER: 1, |
---|
62 | END: 2 |
---|
63 | }; |
---|
64 | |
---|
65 | // Keys "enum" |
---|
66 | var KEY = { |
---|
67 | BACKSPACE: 8, |
---|
68 | TAB: 9, |
---|
69 | ENTER: 13, |
---|
70 | ESCAPE: 27, |
---|
71 | SPACE: 32, |
---|
72 | PAGE_UP: 33, |
---|
73 | PAGE_DOWN: 34, |
---|
74 | END: 35, |
---|
75 | HOME: 36, |
---|
76 | LEFT: 37, |
---|
77 | UP: 38, |
---|
78 | RIGHT: 39, |
---|
79 | DOWN: 40, |
---|
80 | NUMPAD_ENTER: 108, |
---|
81 | COMMA: 188 |
---|
82 | }; |
---|
83 | |
---|
84 | |
---|
85 | // Expose the .tokenInput function to jQuery as a plugin |
---|
86 | $.fn.tokenInput = function (url_or_data, options) { |
---|
87 | var settings = $.extend({}, DEFAULT_SETTINGS, options || {}); |
---|
88 | |
---|
89 | return this.each(function () { |
---|
90 | new $.TokenList(this, url_or_data, settings); |
---|
91 | }); |
---|
92 | }; |
---|
93 | |
---|
94 | |
---|
95 | // TokenList class for each input |
---|
96 | $.TokenList = function (input, url_or_data, settings) { |
---|
97 | // |
---|
98 | // Initialization |
---|
99 | // |
---|
100 | |
---|
101 | // Configure the data source |
---|
102 | if(typeof(url_or_data) === "string") { |
---|
103 | // Set the url to query against |
---|
104 | settings.url = url_or_data; |
---|
105 | |
---|
106 | // Make a smart guess about cross-domain if it wasn't explicitly specified |
---|
107 | if(settings.crossDomain === undefined) { |
---|
108 | if(settings.url.indexOf("://") === -1) { |
---|
109 | settings.crossDomain = false; |
---|
110 | } else { |
---|
111 | settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]); |
---|
112 | } |
---|
113 | } |
---|
114 | } else if(typeof(url_or_data) === "object") { |
---|
115 | // Set the local data to search through |
---|
116 | settings.local_data = url_or_data; |
---|
117 | } |
---|
118 | |
---|
119 | // Build class names |
---|
120 | if(settings.classes) { |
---|
121 | // Use custom class names |
---|
122 | settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes); |
---|
123 | } else if(settings.theme) { |
---|
124 | // Use theme-suffixed default class names |
---|
125 | settings.classes = {}; |
---|
126 | $.each(DEFAULT_CLASSES, function(key, value) { |
---|
127 | settings.classes[key] = value + "-" + settings.theme; |
---|
128 | }); |
---|
129 | } else { |
---|
130 | settings.classes = DEFAULT_CLASSES; |
---|
131 | } |
---|
132 | |
---|
133 | |
---|
134 | // Save the tokens |
---|
135 | var saved_tokens = []; |
---|
136 | |
---|
137 | // Keep track of the number of tokens in the list |
---|
138 | var token_count = 0; |
---|
139 | |
---|
140 | // Basic cache to save on db hits |
---|
141 | var cache = new $.TokenList.Cache(); |
---|
142 | |
---|
143 | // Keep track of the timeout, old vals |
---|
144 | var timeout; |
---|
145 | var input_val; |
---|
146 | |
---|
147 | // Create a new text input an attach keyup events |
---|
148 | var input_box = $("<input type=\"text\" autocomplete=\"off\">") |
---|
149 | .css({ |
---|
150 | outline: "none" |
---|
151 | }) |
---|
152 | .focus(function () { |
---|
153 | if (settings.tokenLimit === null || settings.tokenLimit !== token_count) { |
---|
154 | show_dropdown_hint(); |
---|
155 | } |
---|
156 | }) |
---|
157 | .blur(function () { |
---|
158 | hide_dropdown(); |
---|
159 | }) |
---|
160 | .bind("keyup keydown blur update", resize_input) |
---|
161 | .keydown(function (event) { |
---|
162 | var previous_token; |
---|
163 | var next_token; |
---|
164 | |
---|
165 | switch(event.keyCode) { |
---|
166 | case KEY.LEFT: |
---|
167 | case KEY.RIGHT: |
---|
168 | case KEY.UP: |
---|
169 | case KEY.DOWN: |
---|
170 | if(!$(this).val()) { |
---|
171 | previous_token = input_token.prev(); |
---|
172 | next_token = input_token.next(); |
---|
173 | |
---|
174 | if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) { |
---|
175 | // Check if there is a previous/next token and it is selected |
---|
176 | if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) { |
---|
177 | deselect_token($(selected_token), POSITION.BEFORE); |
---|
178 | } else { |
---|
179 | deselect_token($(selected_token), POSITION.AFTER); |
---|
180 | } |
---|
181 | } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) { |
---|
182 | // We are moving left, select the previous token if it exists |
---|
183 | select_token($(previous_token.get(0))); |
---|
184 | } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) { |
---|
185 | // We are moving right, select the next token if it exists |
---|
186 | select_token($(next_token.get(0))); |
---|
187 | } |
---|
188 | } else { |
---|
189 | var dropdown_item = null; |
---|
190 | |
---|
191 | if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) { |
---|
192 | dropdown_item = $(selected_dropdown_item).next(); |
---|
193 | } else { |
---|
194 | dropdown_item = $(selected_dropdown_item).prev(); |
---|
195 | } |
---|
196 | |
---|
197 | if(dropdown_item.length) { |
---|
198 | select_dropdown_item(dropdown_item); |
---|
199 | } |
---|
200 | return false; |
---|
201 | } |
---|
202 | break; |
---|
203 | |
---|
204 | case KEY.BACKSPACE: |
---|
205 | previous_token = input_token.prev(); |
---|
206 | |
---|
207 | if(!$(this).val().length) { |
---|
208 | if(selected_token) { |
---|
209 | delete_token($(selected_token)); |
---|
210 | } else if(previous_token.length) { |
---|
211 | select_token($(previous_token.get(0))); |
---|
212 | } |
---|
213 | |
---|
214 | return false; |
---|
215 | } else if($(this).val().length === 1) { |
---|
216 | hide_dropdown(); |
---|
217 | } else { |
---|
218 | // set a timeout just long enough to let this function finish. |
---|
219 | setTimeout(function(){do_search();}, 5); |
---|
220 | } |
---|
221 | break; |
---|
222 | |
---|
223 | case KEY.TAB: |
---|
224 | case KEY.ENTER: |
---|
225 | case KEY.NUMPAD_ENTER: |
---|
226 | case KEY.COMMA: |
---|
227 | if(selected_dropdown_item) { |
---|
228 | add_token($(selected_dropdown_item)); |
---|
229 | return false; |
---|
230 | } |
---|
231 | break; |
---|
232 | |
---|
233 | case KEY.ESCAPE: |
---|
234 | hide_dropdown(); |
---|
235 | return true; |
---|
236 | |
---|
237 | default: |
---|
238 | if(String.fromCharCode(event.which)) { |
---|
239 | // set a timeout just long enough to let this function finish. |
---|
240 | setTimeout(function(){do_search();}, 5); |
---|
241 | } |
---|
242 | break; |
---|
243 | } |
---|
244 | }); |
---|
245 | |
---|
246 | if ($(input).get(0).tagName == 'SELECT') { |
---|
247 | // Create a new input to store selected tokens, original will be delete later |
---|
248 | var hidden_input = $("<input type=\"text\" name=\"" + $(input).attr('name') + "\" autocomplete=\"off\">") |
---|
249 | .hide() |
---|
250 | .val("") |
---|
251 | .focus(function () { |
---|
252 | input_box.focus(); |
---|
253 | }) |
---|
254 | .blur(function () { |
---|
255 | input_box.blur(); |
---|
256 | }) |
---|
257 | .insertBefore(input); |
---|
258 | } else { |
---|
259 | // Keep a reference to the original input box |
---|
260 | var hidden_input = $(input) |
---|
261 | .hide() |
---|
262 | .val("") |
---|
263 | .focus(function () { |
---|
264 | input_box.focus(); |
---|
265 | }) |
---|
266 | .blur(function () { |
---|
267 | input_box.blur(); |
---|
268 | }); |
---|
269 | } |
---|
270 | |
---|
271 | // Keep a reference to the selected token and dropdown item |
---|
272 | var selected_token = null; |
---|
273 | var selected_token_index = 0; |
---|
274 | var selected_dropdown_item = null; |
---|
275 | |
---|
276 | // The list to store the token items in |
---|
277 | var token_list = $("<ul />") |
---|
278 | .addClass(settings.classes.tokenList) |
---|
279 | .click(function (event) { |
---|
280 | var li = $(event.target).closest("li"); |
---|
281 | if(li && li.get(0) && $.data(li.get(0), "tokeninput")) { |
---|
282 | toggle_select_token(li); |
---|
283 | } else { |
---|
284 | // Deselect selected token |
---|
285 | if(selected_token) { |
---|
286 | deselect_token($(selected_token), POSITION.END); |
---|
287 | } |
---|
288 | |
---|
289 | // Focus input box |
---|
290 | input_box.focus(); |
---|
291 | } |
---|
292 | }) |
---|
293 | .mouseover(function (event) { |
---|
294 | var li = $(event.target).closest("li"); |
---|
295 | if(li && selected_token !== this) { |
---|
296 | li.addClass(settings.classes.highlightedToken); |
---|
297 | } |
---|
298 | }) |
---|
299 | .mouseout(function (event) { |
---|
300 | var li = $(event.target).closest("li"); |
---|
301 | if(li && selected_token !== this) { |
---|
302 | li.removeClass(settings.classes.highlightedToken); |
---|
303 | } |
---|
304 | }) |
---|
305 | .insertBefore(hidden_input); |
---|
306 | |
---|
307 | // The token holding the input box |
---|
308 | var input_token = $("<li />") |
---|
309 | .addClass(settings.classes.inputToken) |
---|
310 | .appendTo(token_list) |
---|
311 | .append(input_box); |
---|
312 | |
---|
313 | // The list to store the dropdown items in |
---|
314 | var dropdown = $("<div>") |
---|
315 | .addClass(settings.classes.dropdown) |
---|
316 | .appendTo("body") |
---|
317 | .hide(); |
---|
318 | |
---|
319 | // Magic element to help us resize the text input |
---|
320 | var input_resizer = $("<tester/>") |
---|
321 | .insertAfter(input_box) |
---|
322 | .css({ |
---|
323 | position: "absolute", |
---|
324 | top: -9999, |
---|
325 | left: -9999, |
---|
326 | width: "auto", |
---|
327 | fontSize: input_box.css("fontSize"), |
---|
328 | fontFamily: input_box.css("fontFamily"), |
---|
329 | fontWeight: input_box.css("fontWeight"), |
---|
330 | letterSpacing: input_box.css("letterSpacing"), |
---|
331 | whiteSpace: "nowrap" |
---|
332 | }); |
---|
333 | |
---|
334 | // Pre-populate list if items exist |
---|
335 | hidden_input.val(""); |
---|
336 | var li_data = settings.prePopulate || hidden_input.data("pre"); |
---|
337 | if(settings.processPrePopulate && $.isFunction(settings.onResult)) { |
---|
338 | li_data = settings.onResult.call(hidden_input, li_data); |
---|
339 | } |
---|
340 | if(li_data && li_data.length) { |
---|
341 | $.each(li_data, function (index, value) { |
---|
342 | insert_token(value.id, value.name); |
---|
343 | }); |
---|
344 | } |
---|
345 | |
---|
346 | // Pre-populate from SELECT options |
---|
347 | if ($(input).get(0).tagName == 'SELECT') { |
---|
348 | $(input).children('option').each(function () { |
---|
349 | insert_token($(this).attr('value'), $(this).text()); |
---|
350 | }); |
---|
351 | } |
---|
352 | |
---|
353 | |
---|
354 | |
---|
355 | // |
---|
356 | // Private functions |
---|
357 | // |
---|
358 | |
---|
359 | function resize_input() { |
---|
360 | if(input_val === (input_val = input_box.val())) {return;} |
---|
361 | |
---|
362 | // Enter new content into resizer and resize input accordingly |
---|
363 | var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>'); |
---|
364 | input_resizer.html(escaped); |
---|
365 | input_box.width(input_resizer.width() + 30); |
---|
366 | } |
---|
367 | |
---|
368 | function is_printable_character(keycode) { |
---|
369 | return ((keycode >= 48 && keycode <= 90) || // 0-1a-z |
---|
370 | (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * . |
---|
371 | (keycode >= 186 && keycode <= 192) || // ; = , - . / ^ |
---|
372 | (keycode >= 219 && keycode <= 222)); // ( \ ) ' |
---|
373 | } |
---|
374 | |
---|
375 | // Inner function to a token to the list |
---|
376 | function insert_token(id, value) { |
---|
377 | var this_token = $("<li><p>"+ value +"</p></li>") |
---|
378 | .addClass(settings.classes.token) |
---|
379 | .insertBefore(input_token); |
---|
380 | |
---|
381 | // The 'delete token' button |
---|
382 | $("<span>" + settings.deleteText + "</span>") |
---|
383 | .addClass(settings.classes.tokenDelete) |
---|
384 | .appendTo(this_token) |
---|
385 | .click(function () { |
---|
386 | delete_token($(this).parent()); |
---|
387 | return false; |
---|
388 | }); |
---|
389 | |
---|
390 | // Store data on the token |
---|
391 | var token_data = {"id": id, "name": value}; |
---|
392 | $.data(this_token.get(0), "tokeninput", token_data); |
---|
393 | |
---|
394 | // Save this token for duplicate checking |
---|
395 | saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index)); |
---|
396 | selected_token_index++; |
---|
397 | |
---|
398 | // Update the hidden input |
---|
399 | var token_ids = $.map(saved_tokens, function (el) { |
---|
400 | return el.id; |
---|
401 | }); |
---|
402 | hidden_input.val(token_ids.join(settings.tokenDelimiter)); |
---|
403 | |
---|
404 | token_count += 1; |
---|
405 | |
---|
406 | return this_token; |
---|
407 | } |
---|
408 | |
---|
409 | // Add a token to the token list based on user input |
---|
410 | function add_token (item) { |
---|
411 | var li_data = $.data(item.get(0), "tokeninput"); |
---|
412 | var callback = settings.onAdd; |
---|
413 | |
---|
414 | // See if the token already exists and select it if we don't want duplicates |
---|
415 | if(token_count > 0 && settings.preventDuplicates) { |
---|
416 | var found_existing_token = null; |
---|
417 | token_list.children().each(function () { |
---|
418 | var existing_token = $(this); |
---|
419 | var existing_data = $.data(existing_token.get(0), "tokeninput"); |
---|
420 | if(existing_data && existing_data.id === li_data.id) { |
---|
421 | found_existing_token = existing_token; |
---|
422 | return false; |
---|
423 | } |
---|
424 | }); |
---|
425 | |
---|
426 | if(found_existing_token) { |
---|
427 | select_token(found_existing_token); |
---|
428 | input_token.insertAfter(found_existing_token); |
---|
429 | input_box.focus(); |
---|
430 | return; |
---|
431 | } |
---|
432 | } |
---|
433 | |
---|
434 | // Insert the new tokens |
---|
435 | insert_token(li_data.id, li_data.name); |
---|
436 | |
---|
437 | // Check the token limit |
---|
438 | if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) { |
---|
439 | input_box.hide(); |
---|
440 | hide_dropdown(); |
---|
441 | return; |
---|
442 | } else { |
---|
443 | input_box.focus(); |
---|
444 | } |
---|
445 | |
---|
446 | // Clear input box |
---|
447 | input_box.val(""); |
---|
448 | |
---|
449 | // Don't show the help dropdown, they've got the idea |
---|
450 | hide_dropdown(); |
---|
451 | |
---|
452 | // Execute the onAdd callback if defined |
---|
453 | if($.isFunction(callback)) { |
---|
454 | callback.call(hidden_input,li_data); |
---|
455 | } |
---|
456 | } |
---|
457 | |
---|
458 | // Select a token in the token list |
---|
459 | function select_token (token) { |
---|
460 | token.addClass(settings.classes.selectedToken); |
---|
461 | selected_token = token.get(0); |
---|
462 | |
---|
463 | // Hide input box |
---|
464 | input_box.val(""); |
---|
465 | |
---|
466 | // Hide dropdown if it is visible (eg if we clicked to select token) |
---|
467 | hide_dropdown(); |
---|
468 | } |
---|
469 | |
---|
470 | // Deselect a token in the token list |
---|
471 | function deselect_token (token, position) { |
---|
472 | token.removeClass(settings.classes.selectedToken); |
---|
473 | selected_token = null; |
---|
474 | |
---|
475 | if(position === POSITION.BEFORE) { |
---|
476 | input_token.insertBefore(token); |
---|
477 | selected_token_index--; |
---|
478 | } else if(position === POSITION.AFTER) { |
---|
479 | input_token.insertAfter(token); |
---|
480 | selected_token_index++; |
---|
481 | } else { |
---|
482 | input_token.appendTo(token_list); |
---|
483 | selected_token_index = token_count; |
---|
484 | } |
---|
485 | |
---|
486 | // Show the input box and give it focus again |
---|
487 | input_box.focus(); |
---|
488 | } |
---|
489 | |
---|
490 | // Toggle selection of a token in the token list |
---|
491 | function toggle_select_token(token) { |
---|
492 | var previous_selected_token = selected_token; |
---|
493 | |
---|
494 | if(selected_token) { |
---|
495 | deselect_token($(selected_token), POSITION.END); |
---|
496 | } |
---|
497 | |
---|
498 | if(previous_selected_token === token.get(0)) { |
---|
499 | deselect_token(token, POSITION.END); |
---|
500 | } else { |
---|
501 | select_token(token); |
---|
502 | } |
---|
503 | } |
---|
504 | |
---|
505 | // Delete a token from the token list |
---|
506 | function delete_token (token) { |
---|
507 | // Remove the id from the saved list |
---|
508 | var token_data = $.data(token.get(0), "tokeninput"); |
---|
509 | var callback = settings.onDelete; |
---|
510 | |
---|
511 | var index = token.prevAll().length; |
---|
512 | if(index > selected_token_index) index--; |
---|
513 | |
---|
514 | // Delete the token |
---|
515 | token.remove(); |
---|
516 | selected_token = null; |
---|
517 | |
---|
518 | // Show the input box and give it focus again |
---|
519 | input_box.focus(); |
---|
520 | |
---|
521 | // Remove this token from the saved list |
---|
522 | saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1)); |
---|
523 | if(index < selected_token_index) selected_token_index--; |
---|
524 | |
---|
525 | // Update the hidden input |
---|
526 | var token_ids = $.map(saved_tokens, function (el) { |
---|
527 | return el.id; |
---|
528 | }); |
---|
529 | hidden_input.val(token_ids.join(settings.tokenDelimiter)); |
---|
530 | |
---|
531 | token_count -= 1; |
---|
532 | |
---|
533 | if(settings.tokenLimit !== null) { |
---|
534 | input_box |
---|
535 | .show() |
---|
536 | .val("") |
---|
537 | .focus(); |
---|
538 | } |
---|
539 | |
---|
540 | // Execute the onDelete callback if defined |
---|
541 | if($.isFunction(callback)) { |
---|
542 | callback.call(hidden_input,token_data); |
---|
543 | } |
---|
544 | } |
---|
545 | |
---|
546 | // Hide and clear the results dropdown |
---|
547 | function hide_dropdown () { |
---|
548 | dropdown.hide().empty(); |
---|
549 | selected_dropdown_item = null; |
---|
550 | } |
---|
551 | |
---|
552 | function show_dropdown() { |
---|
553 | dropdown |
---|
554 | .css({ |
---|
555 | position: "absolute", |
---|
556 | top: $(token_list).offset().top + $(token_list).outerHeight(), |
---|
557 | left: $(token_list).offset().left, |
---|
558 | zindex: 999 |
---|
559 | }) |
---|
560 | .show(); |
---|
561 | } |
---|
562 | |
---|
563 | function show_dropdown_searching () { |
---|
564 | if(settings.searchingText) { |
---|
565 | dropdown.html("<p>"+settings.searchingText+"</p>"); |
---|
566 | show_dropdown(); |
---|
567 | } |
---|
568 | } |
---|
569 | |
---|
570 | function show_dropdown_hint () { |
---|
571 | if(settings.hintText) { |
---|
572 | dropdown.html("<p>"+settings.hintText+"</p>"); |
---|
573 | show_dropdown(); |
---|
574 | } |
---|
575 | } |
---|
576 | |
---|
577 | // Highlight the query part of the search term |
---|
578 | function highlight_term(value, term) { |
---|
579 | return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>"); |
---|
580 | } |
---|
581 | |
---|
582 | // Populate the results dropdown with some results |
---|
583 | function populate_dropdown (query, results) { |
---|
584 | if(results && results.length) { |
---|
585 | dropdown.empty(); |
---|
586 | var dropdown_ul = $("<ul>") |
---|
587 | .appendTo(dropdown) |
---|
588 | .mouseover(function (event) { |
---|
589 | select_dropdown_item($(event.target).closest("li")); |
---|
590 | }) |
---|
591 | .mousedown(function (event) { |
---|
592 | add_token($(event.target).closest("li")); |
---|
593 | return false; |
---|
594 | }) |
---|
595 | .hide(); |
---|
596 | |
---|
597 | $.each(results, function(index, value) { |
---|
598 | var this_li = $("<li>" + highlight_term(value.name, query) + "</li>") |
---|
599 | .appendTo(dropdown_ul); |
---|
600 | |
---|
601 | if(index % 2) { |
---|
602 | this_li.addClass(settings.classes.dropdownItem); |
---|
603 | } else { |
---|
604 | this_li.addClass(settings.classes.dropdownItem2); |
---|
605 | } |
---|
606 | |
---|
607 | if(index === 0) { |
---|
608 | select_dropdown_item(this_li); |
---|
609 | } |
---|
610 | |
---|
611 | $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name}); |
---|
612 | }); |
---|
613 | |
---|
614 | show_dropdown(); |
---|
615 | |
---|
616 | if(settings.animateDropdown) { |
---|
617 | dropdown_ul.slideDown("fast"); |
---|
618 | } else { |
---|
619 | dropdown_ul.show(); |
---|
620 | } |
---|
621 | } else { |
---|
622 | if(settings.noResultsText) { |
---|
623 | dropdown.html("<p>"+settings.noResultsText+"</p>"); |
---|
624 | show_dropdown(); |
---|
625 | } |
---|
626 | } |
---|
627 | } |
---|
628 | |
---|
629 | // Highlight an item in the results dropdown |
---|
630 | function select_dropdown_item (item) { |
---|
631 | if(item) { |
---|
632 | if(selected_dropdown_item) { |
---|
633 | deselect_dropdown_item($(selected_dropdown_item)); |
---|
634 | } |
---|
635 | |
---|
636 | item.addClass(settings.classes.selectedDropdownItem); |
---|
637 | selected_dropdown_item = item.get(0); |
---|
638 | } |
---|
639 | } |
---|
640 | |
---|
641 | // Remove highlighting from an item in the results dropdown |
---|
642 | function deselect_dropdown_item (item) { |
---|
643 | item.removeClass(settings.classes.selectedDropdownItem); |
---|
644 | selected_dropdown_item = null; |
---|
645 | } |
---|
646 | |
---|
647 | // Do a search and show the "searching" dropdown if the input is longer |
---|
648 | // than settings.minChars |
---|
649 | function do_search() { |
---|
650 | var query = input_box.val().toLowerCase(); |
---|
651 | |
---|
652 | if(query && query.length) { |
---|
653 | if(selected_token) { |
---|
654 | deselect_token($(selected_token), POSITION.AFTER); |
---|
655 | } |
---|
656 | |
---|
657 | if(query.length >= settings.minChars) { |
---|
658 | show_dropdown_searching(); |
---|
659 | clearTimeout(timeout); |
---|
660 | |
---|
661 | timeout = setTimeout(function(){ |
---|
662 | run_search(query); |
---|
663 | }, settings.searchDelay); |
---|
664 | } else { |
---|
665 | hide_dropdown(); |
---|
666 | } |
---|
667 | } |
---|
668 | } |
---|
669 | |
---|
670 | // Do the actual search |
---|
671 | function run_search(query) { |
---|
672 | var cached_results = cache.get(query); |
---|
673 | if(cached_results) { |
---|
674 | populate_dropdown(query, cached_results); |
---|
675 | } else { |
---|
676 | // Are we doing an ajax search or local data search? |
---|
677 | if(settings.url) { |
---|
678 | // Extract exisiting get params |
---|
679 | var ajax_params = {}; |
---|
680 | ajax_params.data = {}; |
---|
681 | if(settings.url.indexOf("?") > -1) { |
---|
682 | var parts = settings.url.split("?"); |
---|
683 | ajax_params.url = parts[0]; |
---|
684 | |
---|
685 | var param_array = parts[1].split("&"); |
---|
686 | $.each(param_array, function (index, value) { |
---|
687 | var kv = value.split("="); |
---|
688 | ajax_params.data[kv[0]] = kv[1]; |
---|
689 | }); |
---|
690 | } else { |
---|
691 | ajax_params.url = settings.url; |
---|
692 | } |
---|
693 | |
---|
694 | // Prepare the request |
---|
695 | ajax_params.data[settings.queryParam] = query; |
---|
696 | ajax_params.type = settings.method; |
---|
697 | ajax_params.dataType = settings.contentType; |
---|
698 | if(settings.crossDomain) { |
---|
699 | ajax_params.dataType = "jsonp"; |
---|
700 | } |
---|
701 | |
---|
702 | // Attach the success callback |
---|
703 | ajax_params.success = function(results) { |
---|
704 | if($.isFunction(settings.onResult)) { |
---|
705 | results = settings.onResult.call(hidden_input, results); |
---|
706 | } |
---|
707 | |
---|
708 | if(settings.allowCreation) { |
---|
709 | results.push({name: input_box.val() + settings.newText, id: input_box.val()}); |
---|
710 | } |
---|
711 | cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results); |
---|
712 | |
---|
713 | // only populate the dropdown if the results are associated with the active search query |
---|
714 | if(input_box.val().toLowerCase() === query) { |
---|
715 | populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results); |
---|
716 | } |
---|
717 | }; |
---|
718 | |
---|
719 | // Make the request |
---|
720 | $.ajax(ajax_params); |
---|
721 | } else if(settings.local_data) { |
---|
722 | // Do the search through local data |
---|
723 | var results = $.grep(settings.local_data, function (row) { |
---|
724 | return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1; |
---|
725 | }); |
---|
726 | |
---|
727 | if($.isFunction(settings.onResult)) { |
---|
728 | results = settings.onResult.call(hidden_input, results); |
---|
729 | } |
---|
730 | |
---|
731 | if(settings.allowCreation) { |
---|
732 | results.push({name: input_box.val() + settings.newText, id: input_box.val()}); |
---|
733 | } |
---|
734 | |
---|
735 | cache.add(query, results); |
---|
736 | |
---|
737 | populate_dropdown(query, results); |
---|
738 | } |
---|
739 | } |
---|
740 | } |
---|
741 | |
---|
742 | if ($(input).get(0).tagName == 'SELECT') { |
---|
743 | $(input).remove(); |
---|
744 | } |
---|
745 | }; |
---|
746 | |
---|
747 | // Really basic cache for the results |
---|
748 | $.TokenList.Cache = function (options) { |
---|
749 | var settings = $.extend({ |
---|
750 | max_size: 500 |
---|
751 | }, options); |
---|
752 | |
---|
753 | var data = {}; |
---|
754 | var size = 0; |
---|
755 | |
---|
756 | var flush = function () { |
---|
757 | data = {}; |
---|
758 | size = 0; |
---|
759 | }; |
---|
760 | |
---|
761 | this.add = function (query, results) { |
---|
762 | if(size > settings.max_size) { |
---|
763 | flush(); |
---|
764 | } |
---|
765 | |
---|
766 | if(!data[query]) { |
---|
767 | size += 1; |
---|
768 | } |
---|
769 | |
---|
770 | data[query] = results; |
---|
771 | }; |
---|
772 | |
---|
773 | this.get = function (query) { |
---|
774 | return data[query]; |
---|
775 | }; |
---|
776 | }; |
---|
777 | }(jQuery)); |
---|