[10970] | 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...", |
---|
[11008] | 24 | newText: "(new)", |
---|
[10970] | 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) { |
---|
[23229] | 579 | return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + escape_regexp_chars(term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>"); |
---|
[10970] | 580 | } |
---|
[23229] | 581 | |
---|
| 582 | function escape_regexp_chars(string) { |
---|
| 583 | var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\ |
---|
| 584 | return string.replace(specials, "\\$&"); |
---|
| 585 | } |
---|
[10970] | 586 | |
---|
| 587 | // Populate the results dropdown with some results |
---|
| 588 | function populate_dropdown (query, results) { |
---|
| 589 | if(results && results.length) { |
---|
| 590 | dropdown.empty(); |
---|
| 591 | var dropdown_ul = $("<ul>") |
---|
| 592 | .appendTo(dropdown) |
---|
| 593 | .mouseover(function (event) { |
---|
| 594 | select_dropdown_item($(event.target).closest("li")); |
---|
| 595 | }) |
---|
| 596 | .mousedown(function (event) { |
---|
| 597 | add_token($(event.target).closest("li")); |
---|
| 598 | return false; |
---|
| 599 | }) |
---|
| 600 | .hide(); |
---|
| 601 | |
---|
| 602 | $.each(results, function(index, value) { |
---|
| 603 | var this_li = $("<li>" + highlight_term(value.name, query) + "</li>") |
---|
| 604 | .appendTo(dropdown_ul); |
---|
| 605 | |
---|
| 606 | if(index % 2) { |
---|
| 607 | this_li.addClass(settings.classes.dropdownItem); |
---|
| 608 | } else { |
---|
| 609 | this_li.addClass(settings.classes.dropdownItem2); |
---|
| 610 | } |
---|
| 611 | |
---|
| 612 | if(index === 0) { |
---|
| 613 | select_dropdown_item(this_li); |
---|
| 614 | } |
---|
| 615 | |
---|
| 616 | $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name}); |
---|
| 617 | }); |
---|
| 618 | |
---|
| 619 | show_dropdown(); |
---|
| 620 | |
---|
| 621 | if(settings.animateDropdown) { |
---|
| 622 | dropdown_ul.slideDown("fast"); |
---|
| 623 | } else { |
---|
| 624 | dropdown_ul.show(); |
---|
| 625 | } |
---|
| 626 | } else { |
---|
| 627 | if(settings.noResultsText) { |
---|
| 628 | dropdown.html("<p>"+settings.noResultsText+"</p>"); |
---|
| 629 | show_dropdown(); |
---|
| 630 | } |
---|
| 631 | } |
---|
| 632 | } |
---|
| 633 | |
---|
| 634 | // Highlight an item in the results dropdown |
---|
| 635 | function select_dropdown_item (item) { |
---|
| 636 | if(item) { |
---|
| 637 | if(selected_dropdown_item) { |
---|
| 638 | deselect_dropdown_item($(selected_dropdown_item)); |
---|
| 639 | } |
---|
| 640 | |
---|
| 641 | item.addClass(settings.classes.selectedDropdownItem); |
---|
| 642 | selected_dropdown_item = item.get(0); |
---|
| 643 | } |
---|
| 644 | } |
---|
| 645 | |
---|
| 646 | // Remove highlighting from an item in the results dropdown |
---|
| 647 | function deselect_dropdown_item (item) { |
---|
| 648 | item.removeClass(settings.classes.selectedDropdownItem); |
---|
| 649 | selected_dropdown_item = null; |
---|
| 650 | } |
---|
| 651 | |
---|
| 652 | // Do a search and show the "searching" dropdown if the input is longer |
---|
| 653 | // than settings.minChars |
---|
| 654 | function do_search() { |
---|
| 655 | var query = input_box.val().toLowerCase(); |
---|
| 656 | |
---|
| 657 | if(query && query.length) { |
---|
| 658 | if(selected_token) { |
---|
| 659 | deselect_token($(selected_token), POSITION.AFTER); |
---|
| 660 | } |
---|
| 661 | |
---|
| 662 | if(query.length >= settings.minChars) { |
---|
| 663 | show_dropdown_searching(); |
---|
| 664 | clearTimeout(timeout); |
---|
| 665 | |
---|
| 666 | timeout = setTimeout(function(){ |
---|
| 667 | run_search(query); |
---|
| 668 | }, settings.searchDelay); |
---|
| 669 | } else { |
---|
| 670 | hide_dropdown(); |
---|
| 671 | } |
---|
| 672 | } |
---|
| 673 | } |
---|
| 674 | |
---|
| 675 | // Do the actual search |
---|
| 676 | function run_search(query) { |
---|
| 677 | var cached_results = cache.get(query); |
---|
| 678 | if(cached_results) { |
---|
| 679 | populate_dropdown(query, cached_results); |
---|
| 680 | } else { |
---|
| 681 | // Are we doing an ajax search or local data search? |
---|
| 682 | if(settings.url) { |
---|
| 683 | // Extract exisiting get params |
---|
| 684 | var ajax_params = {}; |
---|
| 685 | ajax_params.data = {}; |
---|
| 686 | if(settings.url.indexOf("?") > -1) { |
---|
| 687 | var parts = settings.url.split("?"); |
---|
| 688 | ajax_params.url = parts[0]; |
---|
| 689 | |
---|
| 690 | var param_array = parts[1].split("&"); |
---|
| 691 | $.each(param_array, function (index, value) { |
---|
| 692 | var kv = value.split("="); |
---|
| 693 | ajax_params.data[kv[0]] = kv[1]; |
---|
| 694 | }); |
---|
| 695 | } else { |
---|
| 696 | ajax_params.url = settings.url; |
---|
| 697 | } |
---|
| 698 | |
---|
| 699 | // Prepare the request |
---|
| 700 | ajax_params.data[settings.queryParam] = query; |
---|
| 701 | ajax_params.type = settings.method; |
---|
| 702 | ajax_params.dataType = settings.contentType; |
---|
| 703 | if(settings.crossDomain) { |
---|
| 704 | ajax_params.dataType = "jsonp"; |
---|
| 705 | } |
---|
| 706 | |
---|
| 707 | // Attach the success callback |
---|
| 708 | ajax_params.success = function(results) { |
---|
| 709 | if($.isFunction(settings.onResult)) { |
---|
| 710 | results = settings.onResult.call(hidden_input, results); |
---|
| 711 | } |
---|
| 712 | |
---|
| 713 | if(settings.allowCreation) { |
---|
[11008] | 714 | results.push({name: input_box.val() + settings.newText, id: input_box.val()}); |
---|
[10970] | 715 | } |
---|
| 716 | cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results); |
---|
| 717 | |
---|
| 718 | // only populate the dropdown if the results are associated with the active search query |
---|
| 719 | if(input_box.val().toLowerCase() === query) { |
---|
| 720 | populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results); |
---|
| 721 | } |
---|
| 722 | }; |
---|
| 723 | |
---|
| 724 | // Make the request |
---|
| 725 | $.ajax(ajax_params); |
---|
| 726 | } else if(settings.local_data) { |
---|
| 727 | // Do the search through local data |
---|
| 728 | var results = $.grep(settings.local_data, function (row) { |
---|
| 729 | return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1; |
---|
| 730 | }); |
---|
| 731 | |
---|
| 732 | if($.isFunction(settings.onResult)) { |
---|
| 733 | results = settings.onResult.call(hidden_input, results); |
---|
| 734 | } |
---|
| 735 | |
---|
| 736 | if(settings.allowCreation) { |
---|
[11008] | 737 | results.push({name: input_box.val() + settings.newText, id: input_box.val()}); |
---|
[10970] | 738 | } |
---|
| 739 | |
---|
| 740 | cache.add(query, results); |
---|
| 741 | |
---|
| 742 | populate_dropdown(query, results); |
---|
| 743 | } |
---|
| 744 | } |
---|
| 745 | } |
---|
| 746 | |
---|
| 747 | if ($(input).get(0).tagName == 'SELECT') { |
---|
| 748 | $(input).remove(); |
---|
| 749 | } |
---|
| 750 | }; |
---|
| 751 | |
---|
| 752 | // Really basic cache for the results |
---|
| 753 | $.TokenList.Cache = function (options) { |
---|
| 754 | var settings = $.extend({ |
---|
| 755 | max_size: 500 |
---|
| 756 | }, options); |
---|
| 757 | |
---|
| 758 | var data = {}; |
---|
| 759 | var size = 0; |
---|
| 760 | |
---|
| 761 | var flush = function () { |
---|
| 762 | data = {}; |
---|
| 763 | size = 0; |
---|
| 764 | }; |
---|
| 765 | |
---|
| 766 | this.add = function (query, results) { |
---|
| 767 | if(size > settings.max_size) { |
---|
| 768 | flush(); |
---|
| 769 | } |
---|
| 770 | |
---|
| 771 | if(!data[query]) { |
---|
| 772 | size += 1; |
---|
| 773 | } |
---|
| 774 | |
---|
| 775 | data[query] = results; |
---|
| 776 | }; |
---|
| 777 | |
---|
| 778 | this.get = function (query) { |
---|
| 779 | return data[query]; |
---|
| 780 | }; |
---|
| 781 | }; |
---|
| 782 | }(jQuery)); |
---|