/* * jQuery Plugin: Tokenizing Autocomplete Text Entry * Version 1.6.2 * * Copyright (c) 2009 James Smith (http://loopj.com) * Licensed jointly under the GPL and MIT licenses, * choose which one suits your project best! * */ ;(function ($) { var DEFAULT_SETTINGS = { // Search settings method: "GET", queryParam: "q", searchDelay: 300, minChars: 1, propertyToSearch: "name", jsonContainer: null, contentType: "json", excludeCurrent: false, excludeCurrentParameter: "x", // Prepopulation settings prePopulate: null, processPrePopulate: false, // Display settings hintText: "Type in a search term", noResultsText: "No results", searchingText: "Searching...", deleteText: "×", animateDropdown: true, placeholder: null, theme: null, zindex: 999, resultsLimit: null, enableHTML: false, resultsFormatter: function(item) { var string = item[this.propertyToSearch]; return "
  • " + (this.enableHTML ? string : _escapeHTML(string)) + "
  • "; }, tokenFormatter: function(item) { var string = item[this.propertyToSearch]; return "
  • " + (this.enableHTML ? string : _escapeHTML(string)) + "

  • "; }, // Tokenization settings tokenLimit: null, tokenDelimiter: ",", preventDuplicates: false, tokenValue: "id", // Behavioral settings allowFreeTagging: false, allowTabOut: false, autoSelectFirstResult: false, // Callbacks onResult: null, onCachedResult: null, onAdd: null, onFreeTaggingAdd: null, onDelete: null, onReady: null, // Other settings idPrefix: "token-input-", // Keep track if the input is currently in disabled mode disabled: false }; // Default classes to use when theming var DEFAULT_CLASSES = { tokenList : "token-input-list", token : "token-input-token", tokenReadOnly : "token-input-token-readonly", tokenDelete : "token-input-delete-token", selectedToken : "token-input-selected-token", highlightedToken : "token-input-highlighted-token", dropdown : "token-input-dropdown", dropdownItem : "token-input-dropdown-item", dropdownItem2 : "token-input-dropdown-item2", selectedDropdownItem : "token-input-selected-dropdown-item", inputToken : "token-input-input-token", focused : "token-input-focused", disabled : "token-input-disabled" }; // Input box position "enum" var POSITION = { BEFORE : 0, AFTER : 1, END : 2 }; // Keys "enum" var KEY = { BACKSPACE : 8, TAB : 9, ENTER : 13, ESCAPE : 27, SPACE : 32, PAGE_UP : 33, PAGE_DOWN : 34, END : 35, HOME : 36, LEFT : 37, UP : 38, RIGHT : 39, DOWN : 40, NUMPAD_ENTER : 108, COMMA : 188 }; var HTML_ESCAPES = { '&' : '&', '<' : '<', '>' : '>', '"' : '"', "'" : ''', '/' : '/' }; var HTML_ESCAPE_CHARS = /[&<>"'\/]/g; function coerceToString(val) { return String((val === null || val === undefined) ? '' : val); } function _escapeHTML(text) { return coerceToString(text).replace(HTML_ESCAPE_CHARS, function(match) { return HTML_ESCAPES[match]; }); } // Additional public (exposed) methods var methods = { init: function(url_or_data_or_function, options) { var settings = $.extend({}, DEFAULT_SETTINGS, options || {}); return this.each(function () { $(this).data("settings", settings); $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings)); }); }, clear: function() { this.data("tokenInputObject").clear(); return this; }, add: function(item) { this.data("tokenInputObject").add(item); return this; }, remove: function(item) { this.data("tokenInputObject").remove(item); return this; }, get: function() { return this.data("tokenInputObject").getTokens(); }, toggleDisabled: function(disable) { this.data("tokenInputObject").toggleDisabled(disable); return this; }, setOptions: function(options){ $(this).data("settings", $.extend({}, $(this).data("settings"), options || {})); return this; }, destroy: function () { if (this.data("tokenInputObject")) { this.data("tokenInputObject").clear(); var tmpInput = this; var closest = this.parent(); closest.empty(); tmpInput.show(); closest.append(tmpInput); return tmpInput; } } }; // Expose the .tokenInput function to jQuery as a plugin $.fn.tokenInput = function (method) { // Method calling and initialization logic if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else { return methods.init.apply(this, arguments); } }; // TokenList class for each input $.TokenList = function (input, url_or_data, settings) { // // Initialization // // Configure the data source if (typeof(url_or_data) === "string" || typeof(url_or_data) === "function") { // Set the url to query against $(input).data("settings").url = url_or_data; // If the URL is a function, evaluate it here to do our initalization work var url = computeURL(); // Make a smart guess about cross-domain if it wasn't explicitly specified if ($(input).data("settings").crossDomain === undefined && typeof url === "string") { if(url.indexOf("://") === -1) { $(input).data("settings").crossDomain = false; } else { $(input).data("settings").crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]); } } } else if (typeof(url_or_data) === "object") { // Set the local data to search through $(input).data("settings").local_data = url_or_data; } // Build class names if($(input).data("settings").classes) { // Use custom class names $(input).data("settings").classes = $.extend({}, DEFAULT_CLASSES, $(input).data("settings").classes); } else if($(input).data("settings").theme) { // Use theme-suffixed default class names $(input).data("settings").classes = {}; $.each(DEFAULT_CLASSES, function(key, value) { $(input).data("settings").classes[key] = value + "-" + $(input).data("settings").theme; }); } else { $(input).data("settings").classes = DEFAULT_CLASSES; } // Save the tokens var saved_tokens = []; // Keep track of the number of tokens in the list var token_count = 0; // Basic cache to save on db hits var cache = new $.TokenList.Cache(); // Keep track of the timeout, old vals var timeout; var input_val; // Create a new text input an attach keyup events var input_box = $("") .css({ outline: "none" }) .attr("id", $(input).data("settings").idPrefix + input.id) .focus(function () { if ($(input).data("settings").disabled) { return false; } else if ($(input).data("settings").tokenLimit === null || $(input).data("settings").tokenLimit !== token_count) { show_dropdown_hint(); } token_list.addClass($(input).data("settings").classes.focused); }) .blur(function () { hide_dropdown(); if ($(input).data("settings").allowFreeTagging) { add_freetagging_tokens(); } $(this).val(""); token_list.removeClass($(input).data("settings").classes.focused); }) .bind("keyup keydown blur update", resize_input) .keydown(function (event) { var previous_token; var next_token; switch(event.keyCode) { case KEY.LEFT: case KEY.RIGHT: case KEY.UP: case KEY.DOWN: if(this.value.length === 0) { previous_token = input_token.prev(); next_token = input_token.next(); if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) { // Check if there is a previous/next token and it is selected if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) { deselect_token($(selected_token), POSITION.BEFORE); } else { deselect_token($(selected_token), POSITION.AFTER); } } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) { // We are moving left, select the previous token if it exists select_token($(previous_token.get(0))); } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) { // We are moving right, select the next token if it exists select_token($(next_token.get(0))); } } else { var dropdown_item = null; if (event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) { dropdown_item = $(dropdown).find('li').first(); if (selected_dropdown_item) { dropdown_item = $(selected_dropdown_item).next(); } } else { dropdown_item = $(dropdown).find('li').last(); if (selected_dropdown_item) { dropdown_item = $(selected_dropdown_item).prev(); } } select_dropdown_item(dropdown_item); } break; case KEY.BACKSPACE: previous_token = input_token.prev(); if (this.value.length === 0) { if (selected_token) { delete_token($(selected_token)); hiddenInput.change(); } else if(previous_token.length) { select_token($(previous_token.get(0))); } return false; } else if($(this).val().length === 1) { hide_dropdown(); } else { // set a timeout just long enough to let this function finish. setTimeout(function(){ do_search(); }, 5); } break; case KEY.TAB: case KEY.ENTER: case KEY.NUMPAD_ENTER: case KEY.COMMA: if(selected_dropdown_item) { add_token($(selected_dropdown_item).data("tokeninput")); hiddenInput.change(); } else { if ($(input).data("settings").allowFreeTagging) { if($(input).data("settings").allowTabOut && $(this).val() === "") { return true; } else { add_freetagging_tokens(); } } else { $(this).val(""); if($(input).data("settings").allowTabOut) { return true; } } event.stopPropagation(); event.preventDefault(); } return false; case KEY.ESCAPE: hide_dropdown(); return true; default: if (String.fromCharCode(event.which)) { // set a timeout just long enough to let this function finish. setTimeout(function(){ do_search(); }, 5); } break; } }); // Keep reference for placeholder if (settings.placeholder) { input_box.attr("placeholder", settings.placeholder); } // Keep a reference to the original input box var hiddenInput = $(input) .hide() .val("") .focus(function () { focusWithTimeout(input_box); }) .blur(function () { input_box.blur(); //return the object to this can be referenced in the callback functions. return hiddenInput; }) ; // Keep a reference to the selected token and dropdown item var selected_token = null; var selected_token_index = 0; var selected_dropdown_item = null; // The list to store the token items in var token_list = $("