aboutsummaryrefslogtreecommitdiffstats
path: root/themes/default/js/plugins/jquery.tokeninput.js
diff options
context:
space:
mode:
Diffstat (limited to 'themes/default/js/plugins/jquery.tokeninput.js')
-rw-r--r--themes/default/js/plugins/jquery.tokeninput.js701
1 files changed, 511 insertions, 190 deletions
diff --git a/themes/default/js/plugins/jquery.tokeninput.js b/themes/default/js/plugins/jquery.tokeninput.js
index 0c8662b3b..7348639ae 100644
--- a/themes/default/js/plugins/jquery.tokeninput.js
+++ b/themes/default/js/plugins/jquery.tokeninput.js
@@ -1,50 +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
+ 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",
@@ -52,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"
@@ -81,16 +118,82 @@ var KEY = {
COMMA: 188
};
+var HTML_ESCAPES = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#x27;',
+ '/': '&#x2F;'
+};
-// 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) {
@@ -99,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;
}
@@ -145,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) {
@@ -197,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:
@@ -207,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)));
}
@@ -225,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();
@@ -243,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
@@ -275,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")) {
@@ -287,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();
@@ -333,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, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
- 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) {
@@ -372,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;
}
@@ -426,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
@@ -451,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) {
@@ -484,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
@@ -506,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--;
@@ -516,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
@@ -543,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();
@@ -553,30 +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) {
- return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
+ return value.replace(
+ new RegExp(
+ "(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(term) + ")(?![^<>]*>)(?![^&;]+;)",
+ "gi"
+ ), function(match, p1) {
+ return "<b>" + escapeHTML(p1) + "</b>";
+ }
+ );
+ }
+
+ 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
@@ -589,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();
}
}
@@ -633,34 +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().toLowerCase();
+ var query = input_box.val();
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();
}
@@ -669,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("&");
@@ -688,60 +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
- if(input_box.val().toLowerCase() === 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) {
- 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
@@ -775,3 +1095,4 @@ $.TokenList.Cache = function (options) {
};
};
}(jQuery));
+