diff --git a/component.json b/component.json index 29f0ada9..d81f1438 100644 --- a/component.json +++ b/component.json @@ -1,6 +1,6 @@ { "name" : "jquery-tokeninput", - "version" : "1.7.1", + "version" : "1.7.2", "description" : "Tokeninput is a jQuery plugin which allows your users to select multiple items from a predefined list, using autocompletion as they type to find each item.", "main" : [ "./src/jquery.tokeninput.js", "./styles/token-input.css" ], "homepage" : "http://loopj.com/jquery-tokeninput", diff --git a/package.json b/package.json index d8e84fc5..d78c89cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery-tokeninput", - "version": "2.0.1", + "version": "1.7.3", "devDependencies": { "grunt": "~0.4.2", "grunt-contrib-jshint": "~0.6.3", diff --git a/src/jquery.tokeninput.js b/src/jquery.tokeninput.js index 3e9cf519..22f54149 100755 --- a/src/jquery.tokeninput.js +++ b/src/jquery.tokeninput.js @@ -1,6 +1,6 @@ /* * jQuery Plugin: Tokenizing Autocomplete Text Entry - * Version 1.6.2 + * Version 1.7.2 * * Copyright (c) 2009 James Smith (http://loopj.com) * Licensed jointly under the GPL and MIT licenses, @@ -8,1100 +8,1103 @@ * */ ;(function ($) { - var DEFAULT_SETTINGS = { - // Search settings - method: "GET", - queryParam: "q", - searchDelay: 300, - minChars: 1, - caching: true, - 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; + var DEFAULT_SETTINGS = { + // Search settings + method: "GET", + queryParam: "q", + searchDelay: 300, + minChars: 1, + caching: true, + 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(); + }; + + // 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((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); + if (selected_dropdown_item) { + dropdown_item = $(selected_dropdown_item).next(); + } } else { - deselect_token($(selected_token), POSITION.AFTER); + dropdown_item = $(dropdown).find('li').last(); + + if (selected_dropdown_item) { + dropdown_item = $(selected_dropdown_item).prev(); + } } - } 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))); + + select_dropdown_item(dropdown_item); } - } else { - var dropdown_item = null; - if (event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) { - dropdown_item = $(dropdown).find('li').first(); + 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; + } + } + focusWithTimeout(input_box); + event.stopPropagation(); + event.preventDefault(); - if (selected_dropdown_item) { - dropdown_item = $(selected_dropdown_item).next(); } - } else { - dropdown_item = $(dropdown).find('li').last(); + return false; + + case KEY.ESCAPE: + hide_dropdown(); + return true; - if (selected_dropdown_item) { - dropdown_item = $(selected_dropdown_item).prev(); + default: + if (String.fromCharCode(event.which)) { + // set a timeout just long enough to let this function finish. + setTimeout(function(){ do_search(); }, 5); } - } + break; + } + }); - select_dropdown_item(dropdown_item); + // 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 = $("