Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Tagging: update jQuery tokeninput plugin to resolve conflict with cle…

…ditor
  • Loading branch information...
commit 71c4d74e4a3d262d531459592b509f868593ed3f 1 parent df2bb6c
@linc linc authored
Showing with 535 additions and 305 deletions.
  1. +535 −305 plugins/Tagging/jquery.tokeninput.js
View
840 plugins/Tagging/jquery.tokeninput.js 100644 → 100755
@@ -1,6 +1,6 @@
/*
* jQuery Plugin: Tokenizing Autocomplete Text Entry
- * Version 1.1
+ * Version 1.6.0
*
* Copyright (c) 2009 James Smith (http://loopj.com)
* Licensed jointly under the GPL and MIT licenses,
@@ -8,111 +8,198 @@
*
*/
-/* ie7 & ie8 don't support jQuery.trim(). */
-if(typeof String.prototype.trim !== 'function') {
- String.prototype.trim = function() {
- return this.replace(/^\s+|\s+$/, '');
- }
-}
+(function ($) {
+// Default settings
+var DEFAULT_SETTINGS = {
+ // Search settings
+ method: "GET",
+ contentType: "json",
+ queryParam: "q",
+ searchDelay: 300,
+ minChars: 1,
+ propertyToSearch: "name",
+ jsonContainer: null,
+
+ // Display settings
+ hintText: "Type in a search term",
+ noResultsText: "No results",
+ searchingText: "Searching...",
+ deleteText: "×",
+ animateDropdown: true,
+
+ // Tokenization settings
+ tokenLimit: null,
+ tokenDelimiter: ",",
+ preventDuplicates: false,
+
+ // Output settings
+ tokenValue: "id",
+
+ // Prepopulation settings
+ prePopulate: null,
+ processPrePopulate: false,
+
+ // Manipulation settings
+ idPrefix: "token-input-",
+
+ // Formatters
+ resultsFormatter: function(item){ return "<li>" + item[this.propertyToSearch]+ "</li>" },
+ tokenFormatter: function(item) { return "<li><p>" + item[this.propertyToSearch] + "</p></li>" },
+
+ // Callbacks
+ onResult: null,
+ onAdd: null,
+ onDelete: null,
+ onReady: null
+};
+// Default classes to use when theming
+var DEFAULT_CLASSES = {
+ tokenList: "token-input-list",
+ token: "token-input-token",
+ 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"
+};
-(function($) {
+// Input box position "enum"
+var POSITION = {
+ BEFORE: 0,
+ AFTER: 1,
+ END: 2
+};
-$.fn.tokenInput = function (url, options) {
- var settings = $.extend({
- url: url,
- searchDelay: 300,
- minChars: 1,
- maxLength: 0,
- tokenLimit: null,
- jsonContainer: null,
- method: "GET",
- contentType: "json",
- queryParam: "q",
- onResult: null,
- onFocus: null
- }, options);
+// 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
+};
+
+// 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("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();
+ }
+}
- settings.classes = $.extend({
- tokenList: "token-input-list",
- token: "token-input-token",
- 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"
- }, options.classes);
-
- return this.each(function () {
- var list = new $.TokenList(this, settings);
- });
+// 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 = function (input, settings) {
+// TokenList class for each input
+$.TokenList = function (input, url_or_data, settings) {
//
- // Variables
+ // Initialization
//
- // Input box position "enum"
- var POSITION = {
- BEFORE: 0,
- AFTER: 1,
- END: 2
- };
+ // Configure the data source
+ if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
+ // Set the url to query against
+ 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(url.indexOf("://") === -1) {
+ settings.crossDomain = false;
+ } else {
+ 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;
+ }
+
+ // Build class names
+ if(settings.classes) {
+ // Use custom class names
+ settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
+ } else if(settings.theme) {
+ // Use theme-suffixed default class names
+ settings.classes = {};
+ $.each(DEFAULT_CLASSES, function(key, value) {
+ settings.classes[key] = value + "-" + settings.theme;
+ });
+ } else {
+ settings.classes = DEFAULT_CLASSES;
+ }
- // Keys "enum"
- var KEY = {
- BACKSPACE: 8,
- TAB: 9,
- RETURN: 13,
- ESC: 27,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40,
- COMMA: 188,
- SPACE: 32
- };
-
- $(input).bind('BeforeSubmit', function(e, frm){
- var val = $(input_box).val();
- if (val.length)
- add_blank_token(val, val);
- });
- var cancel_request = false;
-
// 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
+ // Keep track of the timeout, old vals
var timeout;
+ var input_val;
// Create a new text input an attach keyup events
- var input_box = $("<input type=\"text\">")
+ var input_box = $("<input type=\"text\" autocomplete=\"off\">")
.css({
outline: "none"
})
+ .attr("id", settings.idPrefix + input.id)
.focus(function () {
- $(token_list).addClass(settings.classes.tokenList + '-focus');
-
- if($.isFunction(settings.onFocus)) {
- settings.onFocus.call(this);
- }
+ if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
+ show_dropdown_hint();
+ }
})
.blur(function () {
hide_dropdown();
- $(token_list).removeClass(settings.classes.tokenList + '-focus');
+ $(this).val("");
})
+ .bind("keyup keydown blur update", resize_input)
.keydown(function (event) {
var previous_token;
var next_token;
@@ -128,22 +215,22 @@ $.TokenList = function (input, settings) {
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) {
+ 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) {
+ } 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) {
+ } 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) {
+ if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
dropdown_item = $(selected_dropdown_item).next();
} else {
dropdown_item = $(selected_dropdown_item).prev();
@@ -162,245 +249,281 @@ $.TokenList = function (input, 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)));
}
return false;
- } else if($(this).val().length == 1) {
+ } else if($(this).val().length === 1) {
hide_dropdown();
} else {
// set a timeout just long enough to let this function finish.
- setTimeout(function(){do_search(false);}, 5);
+ setTimeout(function(){do_search();}, 5);
}
break;
case KEY.TAB:
- case KEY.RETURN:
+ case KEY.ENTER:
+ case KEY.NUMPAD_ENTER:
case KEY.COMMA:
- case KEY.SPACE:
- if (selected_dropdown_item) {
- add_token($(selected_dropdown_item));
- } else {
- var val = $(input_box).val();
- add_blank_token(val, val);
- cancel_request = true;
+ if(selected_dropdown_item) {
+ add_token($(selected_dropdown_item).data("tokeninput"));
+ hidden_input.change();
+ return false;
}
- return false;
break;
- case KEY.ESC:
+ case KEY.ESCAPE:
hide_dropdown();
return true;
default:
- if(is_printable_character(event.keyCode)) {
- // set a timeout just long enough to let this function finish.
- setTimeout(function(){do_search(false);}, 5);
+ if(String.fromCharCode(event.which)) {
+ // set a timeout just long enough to let this function finish.
+ setTimeout(function(){do_search();}, 5);
}
break;
}
});
-
- if (settings.maxLength > 0) {
- $(input_box).attr('maxlength', settings.maxLength);
- }
// Keep a reference to the original input box
var hidden_input = $(input)
- .hide()
- .focus(function () {
- input_box.focus();
- })
- .blur(function () {
- input_box.blur();
- });
+ .hide()
+ .val("")
+ .focus(function () {
+ input_box.focus();
+ })
+ .blur(function () {
+ input_box.blur();
+ });
// 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 = $("<ul />")
.addClass(settings.classes.tokenList)
- .insertAfter(hidden_input)
.click(function (event) {
- var li = get_element_from_event(event, "li");
- if(li && li.get(0) != input_token.get(0)) {
+ var li = $(event.target).closest("li");
+ if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
toggle_select_token(li);
- return false;
} else {
- input_box.focus();
-
+ // Deselect selected token
if(selected_token) {
deselect_token($(selected_token), POSITION.END);
}
+
+ // Focus input box
+ input_box.focus();
}
})
.mouseover(function (event) {
- var li = get_element_from_event(event, "li");
+ var li = $(event.target).closest("li");
if(li && selected_token !== this) {
li.addClass(settings.classes.highlightedToken);
}
})
.mouseout(function (event) {
- var li = get_element_from_event(event, "li");
+ var li = $(event.target).closest("li");
if(li && selected_token !== this) {
li.removeClass(settings.classes.highlightedToken);
}
})
- .mousedown(function (event) {
- // Stop user selecting text on tokens
- var li = get_element_from_event(event, "li");
- if(li){
- return false;
- }
- });
+ .insertBefore(hidden_input);
+ // The token holding the input box
+ var input_token = $("<li />")
+ .addClass(settings.classes.inputToken)
+ .appendTo(token_list)
+ .append(input_box);
// The list to store the dropdown items in
var dropdown = $("<div>")
- .width(token_list.width() + 6)
.addClass(settings.classes.dropdown)
- .insertAfter(token_list)
+ .appendTo("body")
.hide();
- // The token holding the input box
- var input_token = $("<li />")
- .addClass(settings.classes.inputToken)
- .appendTo(token_list)
- .append(input_box);
+ // Magic element to help us resize the text input
+ var input_resizer = $("<tester/>")
+ .insertAfter(input_box)
+ .css({
+ position: "absolute",
+ top: -9999,
+ left: -9999,
+ width: "auto",
+ fontSize: input_box.css("fontSize"),
+ fontFamily: input_box.css("fontFamily"),
+ fontWeight: input_box.css("fontWeight"),
+ letterSpacing: input_box.css("letterSpacing"),
+ whiteSpace: "nowrap"
+ });
- init_list();
+ // 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);
+ }
+ if(li_data && li_data.length) {
+ $.each(li_data, function (index, value) {
+ insert_token(value);
+ checkTokenLimit();
+ });
+ }
+
+ // Initialization is done
+ if($.isFunction(settings.onReady)) {
+ settings.onReady.call();
+ }
//
- // Functions
+ // Public functions
//
+ this.clear = function() {
+ token_list.children("li").each(function() {
+ if ($(this).children("input").length === 0) {
+ delete_token($(this));
+ }
+ });
+ }
- // Pre-populate list if items exist
- function init_list () {
- li_data = hidden_input.val().split(' '); // settings.prePopulate;
- if(li_data && li_data.length) {
- for(var i in li_data) {
- if (li_data[i].trim() != '') {
- token_count++;
- var this_token = $("<li><p>"+li_data[i]+"</p> </li>")
- .addClass(settings.classes.token)
- .insertBefore(input_token);
-
- $("<span>×</span>")
- .addClass(settings.classes.tokenDelete)
- .appendTo(this_token)
- .click(function () {
- delete_token($(this).parent());
- return false;
- });
-
- $.data(this_token.get(0), "tokeninput", {"id": li_data[i], "name": li_data[i]});
-
- // Clear input box
- input_box
- .val("");
-
- // Don't show the help dropdown, they've got the idea
- hide_dropdown();
+ 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;
+ }
- function is_printable_character(keycode) {
- if((keycode >= 48 && keycode <= 90) || // 0-1a-z
- (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
- (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
- (keycode >= 219) // ( \ ) '
- ) {
- return true;
- } else {
- return false;
- }
+ //
+ // Private functions
+ //
+
+ function checkTokenLimit() {
+ if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
+ input_box.hide();
+ hide_dropdown();
+ return;
+ }
}
- // Get an element of a particular type from an event (click/mouseover etc)
- function get_element_from_event (event, element_type) {
- var target = $(event.target);
- var element = null;
+ function resize_input() {
+ if(input_val === (input_val = input_box.val())) {return;}
- if(target.is(element_type)) {
- element = target;
- } else if(target.parent(element_type).length) {
- element = target.parent(element_type+":first");
- }
+ // 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);
+ }
- return element;
+ function is_printable_character(keycode) {
+ return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
+ (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
+ (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
+ (keycode >= 219 && keycode <= 222)); // ( \ ) '
}
// 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);
-
- // The 'delete token' button
- $("<span>×</span>")
- .addClass(settings.classes.tokenDelete)
- .appendTo(this_token)
- .click(function () {
- delete_token($(this).parent());
- return false;
- });
-
- $.data(this_token.get(0), "tokeninput", {"id": id, "name": value});
-
- return this_token;
- }
+ function insert_token(item) {
+ var this_token = settings.tokenFormatter(item);
+ this_token = $(this_token)
+ .addClass(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());
+ hidden_input.change();
+ return false;
+ });
- // Add a token to the token list based on user input
- function add_token (item) {
- var li_data = $.data(item.get(0), "tokeninput");
- var this_token = insert_token(li_data.id, li_data.name);
-
- // Clear input box and make sure it keeps focus
- input_box
- .val("")
- .focus();
+ // Store data on the token
+ var token_data = {"id": item.id};
+ token_data[settings.propertyToSearch] = item[settings.propertyToSearch];
+ $.data(this_token.get(0), "tokeninput", item);
- // Don't show the help dropdown, they've got the idea
- hide_dropdown();
+ // 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
+ update_hidden_input(saved_tokens, hidden_input);
- // Save this token id
- hidden_input.val(hidden_input.val().trim() + ' ' + li_data.name);
-
- token_count++;
-
- if(settings.tokenLimit != null && token_count >= settings.tokenLimit) {
+ token_count += 1;
+
+ // Check the token limit
+ if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
input_box.hide();
hide_dropdown();
}
+
+ return this_token;
}
- function add_blank_token(name) {
- if (name.trim() == '')
- return;
-
- var this_token = insert_token(name, name);
+ // Add a token to the token list based on user input
+ function add_token (item) {
+ var callback = settings.onAdd;
+
+ // See if the token already exists and select it if we don't want duplicates
+ if(token_count > 0 && 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 === item.id) {
+ found_existing_token = existing_token;
+ return false;
+ }
+ });
- // Clear input box and make sure it keeps focus
- input_box
- .val("")
- .focus();
+ if(found_existing_token) {
+ select_token(found_existing_token);
+ input_token.insertAfter(found_existing_token);
+ input_box.focus();
+ return;
+ }
+ }
+
+ // Insert the new tokens
+ if(settings.tokenLimit == null || token_count < settings.tokenLimit) {
+ insert_token(item);
+ checkTokenLimit();
+ }
+
+ // Clear input box
+ input_box.val("");
// Don't show the help dropdown, they've got the idea
hide_dropdown();
- // Save this token id
- hidden_input.val(hidden_input.val().trim() + ' ' + name);
-
- token_count++;
-
- if(settings.tokenLimit != null && token_count >= settings.tokenLimit) {
- input_box.hide();
+ // Execute the onAdd callback if defined
+ if($.isFunction(callback)) {
+ callback.call(hidden_input,item);
}
}
@@ -421,12 +544,15 @@ $.TokenList = function (input, settings) {
token.removeClass(settings.classes.selectedToken);
selected_token = null;
- if(position == POSITION.BEFORE) {
+ if(position === POSITION.BEFORE) {
input_token.insertBefore(token);
- } else if(position == POSITION.AFTER) {
+ selected_token_index--;
+ } else if(position === POSITION.AFTER) {
input_token.insertAfter(token);
+ selected_token_index++;
} else {
input_token.appendTo(token_list);
+ selected_token_index = token_count;
}
// Show the input box and give it focus again
@@ -434,13 +560,16 @@ $.TokenList = function (input, settings) {
}
// Toggle selection of a token in the token list
- function toggle_select_token (token) {
- if(selected_token == token.get(0)) {
+ function toggle_select_token(token) {
+ var previous_selected_token = selected_token;
+
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.END);
+ }
+
+ if(previous_selected_token === token.get(0)) {
deselect_token(token, POSITION.END);
} else {
- if(selected_token) {
- deselect_token($(selected_token), POSITION.END);
- }
select_token(token);
}
}
@@ -449,6 +578,10 @@ $.TokenList = function (input, 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 index = token.prevAll().length;
+ if(index > selected_token_index) index--;
// Delete the token
token.remove();
@@ -457,25 +590,35 @@ $.TokenList = function (input, settings) {
// Show the input box and give it focus again
input_box.focus();
- // Delete this token's id from hidden input
- var str = hidden_input.val()
- var arr = str.split(' ');
- var newstr = '';
- token_count = 0;
- for (var i in arr) {
- if (arr[i] != token_data.name) {
- token_count++;
- newstr += arr[i] + ' ';
- }
- }
- hidden_input.val(newstr.trim());
-
- if (settings.tokenLimit != null) {
+ // Remove this token from the saved list
+ saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
+ if(index < selected_token_index) selected_token_index--;
+
+ // Update the hidden input
+ update_hidden_input(saved_tokens, hidden_input);
+
+ token_count -= 1;
+
+ if(settings.tokenLimit !== null) {
input_box
.show()
.val("")
.focus();
}
+
+ // Execute the onDelete callback if defined
+ if($.isFunction(callback)) {
+ callback.call(hidden_input,token_data);
+ }
+ }
+
+ // Update the hidden input box value
+ function update_hidden_input(saved_tokens, hidden_input) {
+ var token_values = $.map(saved_tokens, function (el) {
+ return el[settings.tokenValue];
+ });
+ hidden_input.val(token_values.join(settings.tokenDelimiter));
+
}
// Hide and clear the results dropdown
@@ -484,51 +627,88 @@ $.TokenList = function (input, settings) {
selected_dropdown_item = null;
}
+ function show_dropdown() {
+ dropdown
+ .css({
+ position: "absolute",
+ top: $(token_list).offset().top + $(token_list).outerHeight(),
+ left: $(token_list).offset().left,
+ zindex: 999
+ })
+ .show();
+ }
+
+ function show_dropdown_searching () {
+ if(settings.searchingText) {
+ dropdown.html("<p>"+settings.searchingText+"</p>");
+ show_dropdown();
+ }
+ }
+
+ function show_dropdown_hint () {
+ if(settings.hintText) {
+ dropdown.html("<p>"+settings.hintText+"</p>");
+ show_dropdown();
+ }
+ }
+
// Highlight the query part of the search term
- function highlight_term(value, term) {
- return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
- }
+ function highlight_term(value, term) {
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
+ }
+
+ function find_value_and_highlight_term(template, value, term) {
+ return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + value + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term));
+ }
// Populate the results dropdown with some results
function populate_dropdown (query, results) {
- if(results.length && !cancel_request) {
+ if(results && results.length) {
dropdown.empty();
var dropdown_ul = $("<ul>")
.appendTo(dropdown)
.mouseover(function (event) {
- select_dropdown_item(get_element_from_event(event, "li"));
+ select_dropdown_item($(event.target).closest("li"));
})
.mousedown(function (event) {
- add_token(get_element_from_event(event, "li"));
+ add_token($(event.target).closest("li").data("tokeninput"));
+ hidden_input.change();
return false;
- });
- // .hide();
+ })
+ .hide();
+
+ $.each(results, function(index, value) {
+ var this_li = settings.resultsFormatter(value);
+
+ this_li = find_value_and_highlight_term(this_li ,value[settings.propertyToSearch], query);
+
+ this_li = $(this_li).appendTo(dropdown_ul);
+
+ if(index % 2) {
+ this_li.addClass(settings.classes.dropdownItem);
+ } else {
+ this_li.addClass(settings.classes.dropdownItem2);
+ }
- for(var i in results) {
- if (results.hasOwnProperty(i)) {
- var this_li = $("<li>"+highlight_term(results[i].name, query)+"</li>")
- .appendTo(dropdown_ul);
+ if(index === 0) {
+ select_dropdown_item(this_li);
+ }
- if(i%2) {
- this_li.addClass(settings.classes.dropdownItem);
- } else {
- this_li.addClass(settings.classes.dropdownItem2);
- }
+ $.data(this_li.get(0), "tokeninput", value);
+ });
- if(i == 0) {
- select_dropdown_item(this_li);
- }
+ show_dropdown();
- $.data(this_li.get(0), "tokeninput", {"id": results[i].id, "name": results[i].name});
- }
+ if(settings.animateDropdown) {
+ dropdown_ul.slideDown("fast");
+ } else {
+ dropdown_ul.show();
}
-
- dropdown.show();
- // dropdown_ul.slideDown("fast");
-
} else {
- cancel_request = false;
- hide_dropdown();
+ if(settings.noResultsText) {
+ dropdown.html("<p>"+settings.noResultsText+"</p>");
+ show_dropdown();
+ }
}
}
@@ -552,20 +732,21 @@ $.TokenList = function (input, settings) {
// Do a search and show the "searching" dropdown if the input is longer
// than settings.minChars
- function do_search(immediate) {
+ function do_search() {
var query = input_box.val().toLowerCase();
- if (query && query.length) {
+ if(query && query.length) {
if(selected_token) {
deselect_token($(selected_token), POSITION.AFTER);
}
- if (query.length >= settings.minChars) {
- if (immediate) {
+
+ if(query.length >= settings.minChars) {
+ show_dropdown_searching();
+ clearTimeout(timeout);
+
+ timeout = setTimeout(function(){
run_search(query);
- } else {
- clearTimeout(timeout);
- timeout = setTimeout(function(){run_search(query);}, settings.searchDelay);
- }
+ }, settings.searchDelay);
} else {
hide_dropdown();
}
@@ -574,32 +755,82 @@ $.TokenList = function (input, 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) {
populate_dropdown(query, cached_results);
} else {
- var queryStringDelimiter = settings.url.indexOf("?") < 0 ? "?" : "&";
- var callback = function(results) {
- if($.isFunction(settings.onResult)) {
- results = settings.onResult.call(this, results);
- }
- cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
- populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
- };
-
- if(settings.method == "POST") {
- $.post(settings.url + queryStringDelimiter + settings.queryParam + "=" + query, {}, callback, settings.contentType);
- } else {
- $.get(settings.url + queryStringDelimiter + settings.queryParam + "=" + query, {}, callback, settings.contentType);
- }
+ // Are we doing an ajax search or local data search?
+ if(settings.url) {
+ var url = computeURL();
+ // Extract exisiting get params
+ var ajax_params = {};
+ ajax_params.data = {};
+ if(url.indexOf("?") > -1) {
+ var parts = url.split("?");
+ ajax_params.url = parts[0];
+
+ var param_array = parts[1].split("&");
+ $.each(param_array, function (index, value) {
+ var kv = value.split("=");
+ ajax_params.data[kv[0]] = kv[1];
+ });
+ } else {
+ 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.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, 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);
+ }
+ };
+
+ // Make the request
+ $.ajax(ajax_params);
+ } else if(settings.local_data) {
+ // Do the search through local data
+ var results = $.grep(settings.local_data, function (row) {
+ return row[settings.propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
+ });
+
+ if($.isFunction(settings.onResult)) {
+ results = settings.onResult.call(hidden_input, results);
+ }
+ cache.add(cache_key, results);
+ populate_dropdown(query, results);
+ }
}
}
+
+ // compute the dynamic URL
+ function computeURL() {
+ var url = settings.url;
+ if(typeof settings.url == 'function') {
+ url = settings.url.call();
+ }
+ return url;
+ }
};
// Really basic cache for the results
$.TokenList.Cache = function (options) {
var settings = $.extend({
- max_size: 50
+ max_size: 500
}, options);
var data = {};
@@ -616,7 +847,7 @@ $.TokenList.Cache = function (options) {
}
if(!data[query]) {
- size++;
+ size += 1;
}
data[query] = results;
@@ -626,5 +857,4 @@ $.TokenList.Cache = function (options) {
return data[query];
};
};
-
-})(jQuery);
Please sign in to comment.
Something went wrong with that request. Please try again.