diff options
Diffstat (limited to '')
-rw-r--r-- | themes/default/js/plugins/jquery.tokeninput.js | 713 |
1 files changed, 509 insertions, 204 deletions
diff --git a/themes/default/js/plugins/jquery.tokeninput.js b/themes/default/js/plugins/jquery.tokeninput.js index 0bbf8336b..7348639ae 100644 --- a/themes/default/js/plugins/jquery.tokeninput.js +++ b/themes/default/js/plugins/jquery.tokeninput.js @@ -1,51 +1,85 @@ -/** - DON'T MAKE AUTOMATIC UPGRADE - This is a merged version of : - + https://github.com/gr2m/jquery-tokeninput/ - + https://github.com/mistic100/jquery-tokeninput/ -*/ - /* * jQuery Plugin: Tokenizing Autocomplete Text Entry - * Version 1.4.2 + * Version 1.6.1 * * Copyright (c) 2009 James Smith (http://loopj.com) * Licensed jointly under the GPL and MIT licenses, * choose which one suits your project best! * + * https://github.com/mistic100/jquery-tokeninput */ (function ($) { // Default settings var DEFAULT_SETTINGS = { - hintText: "Type in a search term", - noResultsText: "No results", - searchingText: "Searching...", - newText: "(new)", - deleteText: "×", + // Search settings + method: "GET", + queryParam: "q", searchDelay: 300, minChars: 1, - tokenLimit: null, + propertyToSearch: "name", jsonContainer: null, - method: "GET", contentType: "json", - queryParam: "q", - tokenDelimiter: ",", - preventDuplicates: false, + + // Prepopulation settings prePopulate: null, processPrePopulate: false, + + // Display settings + hintText: "Type in a search term", + noResultsText: "No results", + searchingText: "Searching...", + deleteText: "×", + newText: " (new)", animateDropdown: true, + placeholder: null, + theme: null, + zindex: 999, + resultsLimit: null, + + enableHTML: false, + + resultsFormatter: function(item) { + var string = item[this.propertyToSearch]; + return "<li>" + (this.enableHTML ? string : _escapeHTML(string)) + "</li>"; + }, + + tokenFormatter: function(item) { + var string = item[this.propertyToSearch]; + return "<li><p>" + (this.enableHTML ? string : _escapeHTML(string)) + "</p></li>"; + }, + + // Tokenization settings + tokenLimit: null, + tokenDelimiter: ",", + preventDuplicates: false, + tokenValue: "id", + + // Behavioral settings + allowFreeTagging: false, + freeTaggingHint: true, + allowTabOut: false, + + // Callbacks onResult: null, + onCachedResult: null, onAdd: null, + onFreeTaggingAdd: null, onDelete: null, - allowCreation: false, - caseSensitive: false + 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", @@ -53,7 +87,9 @@ var DEFAULT_CLASSES = { dropdownItem: "token-input-dropdown-item", dropdownItem2: "token-input-dropdown-item2", selectedDropdownItem: "token-input-selected-dropdown-item", - inputToken: "token-input-input-token" + inputToken: "token-input-input-token", + focused: "token-input-focused", + disabled: "token-input-disabled" }; // Input box position "enum" @@ -82,16 +118,82 @@ var KEY = { COMMA: 188 }; +var HTML_ESCAPES = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' +}; -// Expose the .tokenInput function to jQuery as a plugin -$.fn.tokenInput = function (url_or_data, options) { - var settings = $.extend({}, DEFAULT_SETTINGS, options || {}); +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 () { - new $.TokenList(this, url_or_data, settings); - }); + 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) { @@ -100,35 +202,38 @@ $.TokenList = function (input, url_or_data, settings) { // // Configure the data source - if(typeof(url_or_data) === "string") { + if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") { // Set the url to query against - settings.url = url_or_data; + $(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(settings.crossDomain === undefined) { - if(settings.url.indexOf("://") === -1) { - settings.crossDomain = false; + if($(input).data("settings").crossDomain === undefined && typeof url === "string") { + if(url.indexOf("://") === -1) { + $(input).data("settings").crossDomain = false; } else { - settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]); + $(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 - settings.local_data = url_or_data; + $(input).data("settings").local_data = url_or_data; } // Build class names - if(settings.classes) { + if($(input).data("settings").classes) { // Use custom class names - settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes); - } else if(settings.theme) { + $(input).data("settings").classes = $.extend({}, DEFAULT_CLASSES, $(input).data("settings").classes); + } else if($(input).data("settings").theme) { // Use theme-suffixed default class names - settings.classes = {}; + $(input).data("settings").classes = {}; $.each(DEFAULT_CLASSES, function(key, value) { - settings.classes[key] = value + "-" + settings.theme; + $(input).data("settings").classes[key] = value + "-" + $(input).data("settings").theme; }); } else { - settings.classes = DEFAULT_CLASSES; + $(input).data("settings").classes = DEFAULT_CLASSES; } @@ -146,17 +251,29 @@ $.TokenList = function (input, url_or_data, settings) { var input_val; // Create a new text input an attach keyup events - var input_box = $("<input type=\"text\" autocomplete=\"off\">") + var input_box = $("<input type=\"text\" autocomplete=\"off\" autocapitalize=\"off\">") .css({ outline: "none" }) + .attr("id", $(input).data("settings").idPrefix + input.id) .focus(function () { - if (settings.tokenLimit === null || settings.tokenLimit !== token_count) { + 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) { @@ -198,8 +315,8 @@ $.TokenList = function (input, url_or_data, settings) { if(dropdown_item.length) { select_dropdown_item(dropdown_item); } - return false; } + return false; break; case KEY.BACKSPACE: @@ -208,6 +325,7 @@ $.TokenList = function (input, url_or_data, settings) { if(!$(this).val().length) { if(selected_token) { delete_token($(selected_token)); + hidden_input.change(); } else if(previous_token.length) { select_token($(previous_token.get(0))); } @@ -226,10 +344,25 @@ $.TokenList = function (input, url_or_data, settings) { case KEY.NUMPAD_ENTER: case KEY.COMMA: if(selected_dropdown_item) { - add_token($(selected_dropdown_item)); - return false; + add_token($(selected_dropdown_item).data("tokeninput")); + hidden_input.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(); } - break; + return false; case KEY.ESCAPE: hide_dropdown(); @@ -244,29 +377,52 @@ $.TokenList = function (input, url_or_data, settings) { } }); + // Keep reference for placeholder + if (settings.placeholder) + input_box.attr("placeholder", settings.placeholder) + if ($(input).get(0).tagName == 'SELECT') { - // Create a new input to store selected tokens, original will be delete later - var hidden_input = $("<input type=\"text\" name=\"" + $(input).attr('name') + "\" autocomplete=\"off\">") - .hide() - .val("") - .focus(function () { - input_box.focus(); - }) - .blur(function () { - input_box.blur(); - }) - .insertBefore(input); + // Create a new input to store selected tokens, original will be removed later + var hidden_input = $("<input type=\"text\" name=\"" + $(input).attr('name') + "\" autocomplete=\"off\">") + .hide() + .val("") + .focus(function () { + focus_with_timeout(input_box); + }) + .blur(function () { + input_box.blur(); + return hidden_input; + }) + .insertBefore(input); + + // get prepopulate options and store them in hidden_input + var select_data = []; + $(input).children('option').each(function () { + var item = {}; + item[$(input).data("settings").tokenValue] = $(this).attr('value'); + item[$(input).data("settings").propertyToSearch] = $(this).text(); + select_data[ select_data.length ] = item; + }); + hidden_input.data("pre", select_data); + + // remove the SELECT object + hidden_input.data("settings", $(input).data("settings")); + $(input).remove(); + input = hidden_input[0]; + } else { - // Keep a reference to the original input box - var hidden_input = $(input) - .hide() - .val("") - .focus(function () { - input_box.focus(); - }) - .blur(function () { - input_box.blur(); - }); + // Keep a reference to the original input box + var hidden_input = $(input) + .hide() + .val("") + .focus(function () { + focus_with_timeout(input_box); + }) + .blur(function () { + input_box.blur(); + //return the object to this can be referenced in the callback functions. + return hidden_input; + }); } // Keep a reference to the selected token and dropdown item @@ -276,7 +432,7 @@ $.TokenList = function (input, url_or_data, settings) { // The list to store the token items in var token_list = $("<ul />") - .addClass(settings.classes.tokenList) + .addClass($(input).data("settings").classes.tokenList) .click(function (event) { var li = $(event.target).closest("li"); if(li && li.get(0) && $.data(li.get(0), "tokeninput")) { @@ -288,32 +444,32 @@ $.TokenList = function (input, url_or_data, settings) { } // Focus input box - input_box.focus(); + focus_with_timeout(input_box); } }) .mouseover(function (event) { var li = $(event.target).closest("li"); if(li && selected_token !== this) { - li.addClass(settings.classes.highlightedToken); + li.addClass($(input).data("settings").classes.highlightedToken); } }) .mouseout(function (event) { var li = $(event.target).closest("li"); if(li && selected_token !== this) { - li.removeClass(settings.classes.highlightedToken); + li.removeClass($(input).data("settings").classes.highlightedToken); } }) .insertBefore(hidden_input); // The token holding the input box var input_token = $("<li />") - .addClass(settings.classes.inputToken) + .addClass($(input).data("settings").classes.inputToken) .appendTo(token_list) .append(input_box); // The list to store the dropdown items in var dropdown = $("<div>") - .addClass(settings.classes.dropdown) + .addClass($(input).data("settings").classes.dropdown) .appendTo("body") .hide(); @@ -334,36 +490,116 @@ $.TokenList = function (input, url_or_data, settings) { // Pre-populate list if items exist hidden_input.val(""); - var li_data = settings.prePopulate || hidden_input.data("pre"); - if(settings.processPrePopulate && $.isFunction(settings.onResult)) { - li_data = settings.onResult.call(hidden_input, li_data); - } + var li_data = $(input).data("settings").prePopulate || hidden_input.data("pre"); + if($(input).data("settings").processPrePopulate && $.isFunction($(input).data("settings").onResult)) { + li_data = $(input).data("settings").onResult.call(hidden_input, li_data); + } if(li_data && li_data.length) { $.each(li_data, function (index, value) { - insert_token(value.id, value.name); + insert_token(value); + checkTokenLimit(); + input_box.attr("placeholder", null) }); } - - // Pre-populate from SELECT options - if ($(input).get(0).tagName == 'SELECT') { - $(input).children('option').each(function () { - insert_token($(this).attr('value'), $(this).text()); - }); + + // Check if widget should initialize as disabled + if ($(input).data("settings").disabled) { + toggleDisabled(true); } + // Initialization is done + if($.isFunction($(input).data("settings").onReady)) { + $(input).data("settings").onReady.call(); + } + + // + // Public functions + // + + this.clear = function() { + token_list.children("li").each(function() { + if ($(this).children("input").length === 0) { + delete_token($(this)); + } + }); + }; + this.add = function(item) { + add_token(item); + }; + + this.remove = function(item) { + token_list.children("li").each(function() { + if ($(this).children("input").length === 0) { + var currToken = $(this).data("tokeninput"); + var match = true; + for (var prop in item) { + if (item[prop] !== currToken[prop]) { + match = false; + break; + } + } + if (match) { + delete_token($(this)); + } + } + }); + }; + + this.getTokens = function() { + return saved_tokens; + }; + + this.toggleDisabled = function(disable) { + toggleDisabled(disable); + }; + + // Resize input to maximum width so the placeholder can be seen + resize_input(); // // Private functions // + function escapeHTML(text) { + return $(input).data("settings").enableHTML ? text : _escapeHTML(text); + } + + // Toggles the widget between enabled and disabled state, or according + // to the [disable] parameter. + function toggleDisabled(disable) { + if (typeof disable === 'boolean') { + $(input).data("settings").disabled = disable + } else { + $(input).data("settings").disabled = !$(input).data("settings").disabled; + } + input_box.attr('disabled', $(input).data("settings").disabled); + token_list.toggleClass($(input).data("settings").classes.disabled, $(input).data("settings").disabled); + // if there is any token selected we deselect it + if(selected_token) { + deselect_token($(selected_token), POSITION.END); + } + hidden_input.attr('disabled', $(input).data("settings").disabled); + } + + function checkTokenLimit() { + if($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) { + input_box.hide(); + hide_dropdown(); + return; + } + } + function resize_input() { if(input_val === (input_val = input_box.val())) {return;} + // Get width left on the current line + var width_left = token_list.width() - input_box.offset().left - token_list.offset().left; // Enter new content into resizer and resize input accordingly - var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>'); - input_resizer.html(escaped); - input_box.width(input_resizer.width() + 30); + input_resizer.html(_escapeHTML(input_val)); + // Get maximum width, minimum the size of input and maximum the widget's width + input_box.width(Math.min(token_list.width(), + Math.max(width_left, input_resizer.width() + 30))); } function is_printable_character(keycode) { @@ -373,52 +609,79 @@ $.TokenList = function (input, url_or_data, settings) { (keycode >= 219 && keycode <= 222)); // ( \ ) ' } + function add_freetagging_tokens() { + var value = $.trim(input_box.val()); + var tokens = value.split($(input).data("settings").tokenDelimiter); + $.each(tokens, function(i, token) { + if (!token) { + return; + } + + if ($.isFunction($(input).data("settings").onFreeTaggingAdd)) { + token = $(input).data("settings").onFreeTaggingAdd.call(hidden_input, token); + } + var object = {}; + object[$(input).data("settings").tokenValue] = object[$(input).data("settings").propertyToSearch] = token; + add_token(object); + }); + } + // Inner function to a token to the list - function insert_token(id, value) { - var this_token = $("<li><p>"+ value +"</p></li>") - .addClass(settings.classes.token) - .insertBefore(input_token); + function insert_token(item) { + var $this_token = $($(input).data("settings").tokenFormatter(item)); + var readonly = item.readonly === true ? true : false; + + if(readonly) $this_token.addClass($(input).data("settings").classes.tokenReadOnly); + + $this_token.addClass($(input).data("settings").classes.token).insertBefore(input_token); // The 'delete token' button - $("<span>" + settings.deleteText + "</span>") - .addClass(settings.classes.tokenDelete) - .appendTo(this_token) - .click(function () { - delete_token($(this).parent()); - return false; - }); + if(!readonly) { + $("<span>" + $(input).data("settings").deleteText + "</span>") + .addClass($(input).data("settings").classes.tokenDelete) + .appendTo($this_token) + .click(function () { + if (!$(input).data("settings").disabled) { + delete_token($(this).parent()); + hidden_input.change(); + return false; + } + }); + } // Store data on the token - var token_data = {"id": id, "name": value}; - $.data(this_token.get(0), "tokeninput", token_data); + var token_data = item; + $.data($this_token.get(0), "tokeninput", item); // Save this token for duplicate checking saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index)); selected_token_index++; // Update the hidden input - var token_ids = $.map(saved_tokens, function (el) { - return el.id; - }); - hidden_input.val(token_ids.join(settings.tokenDelimiter)); + update_hidden_input(saved_tokens, hidden_input); token_count += 1; - return this_token; + // Check the token limit + if($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) { + input_box.hide(); + hide_dropdown(); + } + + return $this_token; } // Add a token to the token list based on user input function add_token (item) { - var li_data = $.data(item.get(0), "tokeninput"); - var callback = settings.onAdd; + var callback = $(input).data("settings").onAdd; // See if the token already exists and select it if we don't want duplicates - if(token_count > 0 && settings.preventDuplicates) { + if(token_count > 0 && $(input).data("settings").preventDuplicates) { var found_existing_token = null; token_list.children().each(function () { var existing_token = $(this); var existing_data = $.data(existing_token.get(0), "tokeninput"); - if(existing_data && existing_data.id === li_data.id) { + if(existing_data && existing_data[settings.tokenValue] === item[settings.tokenValue]) { found_existing_token = existing_token; return false; } @@ -427,21 +690,20 @@ $.TokenList = function (input, url_or_data, settings) { if(found_existing_token) { select_token(found_existing_token); input_token.insertAfter(found_existing_token); - input_box.focus(); + focus_with_timeout(input_box); return; } } - // Insert the new tokens - insert_token(li_data.id, li_data.name); + // Squeeze input_box so we force no unnecessary line break + input_box.width(0); - // Check the token limit - if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) { - input_box.hide(); - hide_dropdown(); - return; - } else { - input_box.focus(); + // Insert the new tokens + if($(input).data("settings").tokenLimit == null || token_count < $(input).data("settings").tokenLimit) { + insert_token(item); + // Remove the placeholder so it's not seen after you've added a token + input_box.attr("placeholder", null) + checkTokenLimit(); } // Clear input box @@ -452,25 +714,27 @@ $.TokenList = function (input, url_or_data, settings) { // Execute the onAdd callback if defined if($.isFunction(callback)) { - callback.call(hidden_input,li_data); + callback.call(hidden_input,item); } } // Select a token in the token list function select_token (token) { - token.addClass(settings.classes.selectedToken); - selected_token = token.get(0); + if (!$(input).data("settings").disabled) { + token.addClass($(input).data("settings").classes.selectedToken); + selected_token = token.get(0); - // Hide input box - input_box.val(""); + // Hide input box + input_box.val(""); - // Hide dropdown if it is visible (eg if we clicked to select token) - hide_dropdown(); + // Hide dropdown if it is visible (eg if we clicked to select token) + hide_dropdown(); + } } // Deselect a token in the token list function deselect_token (token, position) { - token.removeClass(settings.classes.selectedToken); + token.removeClass($(input).data("settings").classes.selectedToken); selected_token = null; if(position === POSITION.BEFORE) { @@ -485,7 +749,7 @@ $.TokenList = function (input, url_or_data, settings) { } // Show the input box and give it focus again - input_box.focus(); + focus_with_timeout(input_box); } // Toggle selection of a token in the token list @@ -507,7 +771,7 @@ $.TokenList = function (input, url_or_data, settings) { function delete_token (token) { // Remove the id from the saved list var token_data = $.data(token.get(0), "tokeninput"); - var callback = settings.onDelete; + var callback = $(input).data("settings").onDelete; var index = token.prevAll().length; if(index > selected_token_index) index--; @@ -517,25 +781,25 @@ $.TokenList = function (input, url_or_data, settings) { selected_token = null; // Show the input box and give it focus again - input_box.focus(); + focus_with_timeout(input_box); // Remove this token from the saved list saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1)); + if (saved_tokens.length == 0) { + input_box.attr("placeholder", settings.placeholder) + } if(index < selected_token_index) selected_token_index--; // Update the hidden input - var token_ids = $.map(saved_tokens, function (el) { - return el.id; - }); - hidden_input.val(token_ids.join(settings.tokenDelimiter)); + update_hidden_input(saved_tokens, hidden_input); token_count -= 1; - if(settings.tokenLimit !== null) { + if($(input).data("settings").tokenLimit !== null) { input_box .show() - .val("") - .focus(); + .val(""); + focus_with_timeout(input_box); } // Execute the onDelete callback if defined @@ -544,6 +808,18 @@ $.TokenList = function (input, url_or_data, settings) { } } + // Update the hidden input box value + function update_hidden_input(saved_tokens, hidden_input) { + var token_values = $.map(saved_tokens, function (el) { + if(typeof $(input).data("settings").tokenValue == 'function') + return $(input).data("settings").tokenValue.call(this, el); + + return el[$(input).data("settings").tokenValue]; + }); + hidden_input.val(token_values.join($(input).data("settings").tokenDelimiter)); + + } + // Hide and clear the results dropdown function hide_dropdown () { dropdown.hide().empty(); @@ -554,37 +830,47 @@ $.TokenList = function (input, url_or_data, settings) { dropdown .css({ position: "absolute", - top: $(token_list).offset().top + $(token_list).outerHeight(), - left: $(token_list).offset().left, - zindex: 999 + top: token_list.offset().top + token_list.outerHeight(), + left: token_list.offset().left, + width: token_list.width(), + 'z-index': $(input).data("settings").zindex }) .show(); } function show_dropdown_searching () { - if(settings.searchingText) { - dropdown.html("<p>"+settings.searchingText+"</p>"); + if($(input).data("settings").searchingText) { + dropdown.html("<p>" + escapeHTML($(input).data("settings").searchingText) + "</p>"); show_dropdown(); } } function show_dropdown_hint () { - if(settings.hintText) { - dropdown.html("<p>"+settings.hintText+"</p>"); + if($(input).data("settings").hintText) { + dropdown.html("<p>" + escapeHTML($(input).data("settings").hintText) + "</p>"); show_dropdown(); } } + var regexp_special_chars = new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'); + function regexp_escape(term) { + return term.replace(regexp_special_chars, '\\$&'); + } + // Highlight the query part of the search term function highlight_term(value, term) { - var param = "g"; - if (!settings.caseSensitive) param+= "i"; - return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + escape_regexp_chars(term) + ")(?![^<>]*>)(?![^&;]+;)", param), "<b>$1</b>"); + return value.replace( + new RegExp( + "(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(term) + ")(?![^<>]*>)(?![^&;]+;)", + "gi" + ), function(match, p1) { + return "<b>" + escapeHTML(p1) + "</b>"; + } + ); } - - function escape_regexp_chars(string) { - var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\ - return string.replace(specials, "\\$&"); + + function find_value_and_highlight_term(template, value, term) { + return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(value) + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term)); } // Populate the results dropdown with some results @@ -597,38 +883,46 @@ $.TokenList = function (input, url_or_data, settings) { select_dropdown_item($(event.target).closest("li")); }) .mousedown(function (event) { - add_token($(event.target).closest("li")); + add_token($(event.target).closest("li").data("tokeninput")); + hidden_input.change(); return false; }) .hide(); + if ($(input).data("settings").resultsLimit && results.length > $(input).data("settings").resultsLimit) { + results = results.slice(0, $(input).data("settings").resultsLimit); + } + $.each(results, function(index, value) { - var this_li = $("<li>" + highlight_term(value.name, query) + "</li>") - .appendTo(dropdown_ul); + var this_li = $(input).data("settings").resultsFormatter(value); + + this_li = find_value_and_highlight_term(this_li ,value[$(input).data("settings").propertyToSearch], query); + + this_li = $(this_li).appendTo(dropdown_ul); if(index % 2) { - this_li.addClass(settings.classes.dropdownItem); + this_li.addClass($(input).data("settings").classes.dropdownItem); } else { - this_li.addClass(settings.classes.dropdownItem2); + this_li.addClass($(input).data("settings").classes.dropdownItem2); } if(index === 0) { select_dropdown_item(this_li); } - $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name}); + $.data(this_li.get(0), "tokeninput", value); }); show_dropdown(); - if(settings.animateDropdown) { + if($(input).data("settings").animateDropdown) { dropdown_ul.slideDown("fast"); } else { dropdown_ul.show(); } } else { - if(settings.noResultsText) { - dropdown.html("<p>"+settings.noResultsText+"</p>"); + if($(input).data("settings").noResultsText) { + dropdown.html("<p>" + escapeHTML($(input).data("settings").noResultsText) + "</p>"); show_dropdown(); } } @@ -641,35 +935,34 @@ $.TokenList = function (input, url_or_data, settings) { deselect_dropdown_item($(selected_dropdown_item)); } - item.addClass(settings.classes.selectedDropdownItem); + item.addClass($(input).data("settings").classes.selectedDropdownItem); selected_dropdown_item = item.get(0); } } // Remove highlighting from an item in the results dropdown function deselect_dropdown_item (item) { - item.removeClass(settings.classes.selectedDropdownItem); + item.removeClass($(input).data("settings").classes.selectedDropdownItem); selected_dropdown_item = null; } // Do a search and show the "searching" dropdown if the input is longer - // than settings.minChars + // than $(input).data("settings").minChars function do_search() { var query = input_box.val(); - if (!settings.caseSensitive) query = query.toLowerCase(); if(query && query.length) { if(selected_token) { deselect_token($(selected_token), POSITION.AFTER); } - if(query.length >= settings.minChars) { + if(query.length >= $(input).data("settings").minChars) { show_dropdown_searching(); clearTimeout(timeout); timeout = setTimeout(function(){ run_search(query); - }, settings.searchDelay); + }, $(input).data("settings").searchDelay); } else { hide_dropdown(); } @@ -678,17 +971,22 @@ $.TokenList = function (input, url_or_data, settings) { // Do the actual search function run_search(query) { - var cached_results = cache.get(query); + var cache_key = query + computeURL(); + var cached_results = cache.get(cache_key); if(cached_results) { + if ($.isFunction($(input).data("settings").onCachedResult)) { + cached_results = $(input).data("settings").onCachedResult.call(hidden_input, cached_results); + } populate_dropdown(query, cached_results); } else { // Are we doing an ajax search or local data search? - if(settings.url) { + if($(input).data("settings").url) { + var url = computeURL(); // Extract exisiting get params var ajax_params = {}; ajax_params.data = {}; - if(settings.url.indexOf("?") > -1) { - var parts = settings.url.split("?"); + if(url.indexOf("?") > -1) { + var parts = url.split("?"); ajax_params.url = parts[0]; var param_array = parts[1].split("&"); @@ -697,67 +995,73 @@ $.TokenList = function (input, url_or_data, settings) { ajax_params.data[kv[0]] = kv[1]; }); } else { - ajax_params.url = settings.url; + ajax_params.url = url; } // Prepare the request - ajax_params.data[settings.queryParam] = query; - ajax_params.type = settings.method; - ajax_params.dataType = settings.contentType; - if(settings.crossDomain) { + ajax_params.data[$(input).data("settings").queryParam] = query; + ajax_params.type = $(input).data("settings").method; + ajax_params.dataType = $(input).data("settings").contentType; + if($(input).data("settings").crossDomain) { ajax_params.dataType = "jsonp"; } // Attach the success callback ajax_params.success = function(results) { - if($.isFunction(settings.onResult)) { - results = settings.onResult.call(hidden_input, results); + cache.add(cache_key, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results); + if($.isFunction($(input).data("settings").onResult)) { + results = $(input).data("settings").onResult.call(hidden_input, results); } - if(settings.allowCreation) { - results.push({name: input_box.val() + settings.newText, id: input_box.val()}); + if($(input).data("settings").allowFreeTagging && $(input).data("settings").freeTaggingHint) { + results.push({name: input_box.val() + $(input).data("settings").newText, id: input_box.val()}); } - cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results); // only populate the dropdown if the results are associated with the active search query - var value = input_box.val(); - if (!settings.caseSensitive) value = value.toLowerCase(); - if(value === query) { - populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results); + if(input_box.val() === query) { + populate_dropdown(query, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results); } }; // Make the request $.ajax(ajax_params); - } else if(settings.local_data) { + } else if($(input).data("settings").local_data) { // Do the search through local data - var results = $.grep(settings.local_data, function (row) { - if (settings.caseSensitive) { - return row.name.indexOf(query) > -1; - } - else { - return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1; - } + var results = $.grep($(input).data("settings").local_data, function (row) { + return row[$(input).data("settings").propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1; }); - - if($.isFunction(settings.onResult)) { - results = settings.onResult.call(hidden_input, results); - } - if(settings.allowCreation) { - results.push({name: input_box.val() + settings.newText, id: input_box.val()}); + if($(input).data("settings").allowFreeTagging && $(input).data("settings").freeTaggingHint) { + results.push({name: input_box.val() + $(input).data("settings").newText, id: input_box.val()}); + } + + cache.add(cache_key, results); + if($.isFunction($(input).data("settings").onResult)) { + results = $(input).data("settings").onResult.call(hidden_input, results); } - - cache.add(query, results); - populate_dropdown(query, results); } } } - - if ($(input).get(0).tagName == 'SELECT') { - $(input).remove(); + + // compute the dynamic URL + function computeURL() { + var url = $(input).data("settings").url; + if(typeof $(input).data("settings").url == 'function') { + url = $(input).data("settings").url.call($(input).data("settings")); + } + return url; } + + // Bring browser focus to the specified object. + // Use of setTimeout is to get around an IE bug. + // (See, e.g., http://stackoverflow.com/questions/2600186/focus-doesnt-work-in-ie) + // + // obj: a jQuery object to focus() + function focus_with_timeout(obj) { + setTimeout(function() { obj.focus(); }, 50); + } + }; // Really basic cache for the results @@ -791,3 +1095,4 @@ $.TokenList.Cache = function (options) { }; }; }(jQuery)); + |