aboutsummaryrefslogtreecommitdiffstats
path: root/themes
diff options
context:
space:
mode:
authormistic100 <mistic@piwigo.org>2011-05-25 13:33:29 +0000
committermistic100 <mistic@piwigo.org>2011-05-25 13:33:29 +0000
commit5257aad873f71bb0125d8b576748c7bf1bf3163d (patch)
tree32c944f23b086d796bf88dd45c4aa6243671b7c4 /themes
parentd17ffa839faf5435751fcef153b445ed25b20d52 (diff)
bug:2278 fixed (merge r10970 r11008 r11039 from trunk) replace FCBKcomplete by TokenInput to avoid 3rd tag issue on autocomplete
git-svn-id: http://piwigo.org/svn/branches/2.2@11056 68402e56-0260-453c-a942-63ccdbb3a9ee
Diffstat (limited to 'themes')
-rw-r--r--themes/default/js/plugins/jquery.fcbkcomplete.js645
-rw-r--r--themes/default/js/plugins/jquery.tokeninput.js777
2 files changed, 777 insertions, 645 deletions
diff --git a/themes/default/js/plugins/jquery.fcbkcomplete.js b/themes/default/js/plugins/jquery.fcbkcomplete.js
deleted file mode 100644
index 3fb094305..000000000
--- a/themes/default/js/plugins/jquery.fcbkcomplete.js
+++ /dev/null
@@ -1,645 +0,0 @@
-/**
- FCBKcomplete 2.7.5
- - Jquery version required: 1.2.x, 1.3.x, 1.4.x
-
- Based on TextboxList by Guillermo Rauch http://devthought.com/
-
- Changelog:
- - 2.00 new version of fcbkcomplete
-
- - 2.01 fixed bugs & added features
- fixed filter bug for preadded items
- focus on the input after selecting tag
- the element removed pressing backspace when the element is selected
- input tag in the control has a border in IE7
- added iterate over each match and apply the plugin separately
- set focus on the input after selecting tag
-
- - 2.02 fixed fist element selected bug
- fixed defaultfilter error bug
-
- - 2.5 removed selected="selected" attribute due ie bug
- element search algorithm changed
- better performance fix added
- fixed many small bugs
- onselect event added
- onremove event added
-
- - 2.6 ie6/7 support fix added
- added new public method addItem due request
- added new options "firstselected" that you can set true/false to select first element on dropdown list
- autoexpand input element added
- removeItem bug fixed
- and many more bug fixed
- fixed public method to use it $("elem").trigger("addItem",[{"title": "test", "value": "test"}]);
-
-- 2.7 jquery 1.4 compability
- item lock possability added by adding locked class to preadded option <option value="value" class="selected locked">text</option>
- maximum item that can be added
-
-- 2.7.1 bug fixed
- ajax delay added thanks to http://github.com/dolorian
-
-- 2.7.2 some minor bug fixed
- minified version recompacted due some problems
-
-- 2.7.3 event call fixed thanks to William Parry <williamparry!at!gmail.com>
-
-- 2.7.4 standart event change call added on addItem, removeItem
- preSet also check if item have "selected" attribute
- addItem minor fix
-
-- 2.7.5 event call removeItem fixed
- new public method destroy added needed to remove fcbkcomplete element from dome
-
- */
-/* Coded by: emposha <admin@emposha.com> */
-/* Copyright: Emposha.com <http://www.emposha.com> - Distributed under MIT - Keep this message! */
-
-/**
- * json_url - url to fetch json object
- * cache - use cache
- * height - maximum number of element shown before scroll will apear
- * newel - show typed text like a element
- * firstselected - automaticly select first element from dropdown
- * filter_case - case sensitive filter
- * filter_selected - filter selected items from list
- * complete_text - text for complete page
- * maxshownitems - maximum numbers that will be shown at dropdown list (less better performance)
- * onselect - fire event on item select
- * onremove - fire event on item remove
- * maxitimes - maximum items that can be added
- * delay - delay between ajax request (bigger delay, lower server time request)
- * addontab - add first visible element on tab or enter hit
- * attachto - after this element fcbkcomplete insert own elements
- */
-jQuery(function($) {
- $.fn.fcbkcomplete = function(opt) {
- return this.each(function() {
- function init() {
- createFCBK();
- preSet();
- addInput(0);
- }
-
- function createFCBK() {
- element.hide();
- element.attr("multiple", "multiple");
- if (element.attr("name").indexOf("[]") == -1) {
- element.attr("name", element.attr("name") + "[]");
- }
-
- holder = $(document.createElement("ul"));
- holder.attr("class", "holder");
-
- if (options.attachto) {
- if (typeof(options.attachto) == "object") {
- options.attachto.append(holder);
- }
- else {
- $(options.attachto).append(holder);
- }
-
- }
- else {
- element.after(holder);
- }
-
- complete = $(document.createElement("div"));
- complete.addClass("facebook-auto");
- complete.append('<div class="default">' + options.complete_text + "</div>");
- complete.hover(function() {options.complete_hover = 0;}, function() {options.complete_hover = 1;});
-
- feed = $(document.createElement("ul"));
- feed.attr("id", elemid + "_feed");
-
- complete.prepend(feed);
- holder.after(complete);
- feed.css("width", complete.width());
- }
-
- function preSet() {
- element.children("option").each(function(i, option) {
- option = $(option);
- if (option.hasClass("selected")) {
- addItem(option.text(), option.val(), true, option.hasClass("locked"));
- option.attr("selected", "selected");
- }
- cache.push({
- key: option.text(),
- value: option.val()
- });
- search_string += "" + (cache.length - 1) + ":" + option.text() + ";";
- });
- }
-
- //public method to add new item
- $(this).bind("addItem",
- function(event, data) {
- addItem(data.title, data.value, 0, 0, 0);
- });
-
- //public method to remove item
- $(this).bind("removeItem",
- function(event, data) {
- var item = holder.children('li[rel=' + data.value + ']');
- if (item.length) {
- removeItem(item);
- }
- });
-
- //public method to remove item
- $(this).bind("destroy",
- function(event, data) {
- holder.remove();
- complete.remove();
- element.show();
- });
-
- function addItem(title, value, preadded, locked, focusme) {
- if (!maxItems()) {
- return false;
- }
- var li = document.createElement("li");
- var txt = document.createTextNode(title);
- var aclose = document.createElement("a");
- var liclass = "bit-box" + (locked ? " locked": "");
- $(li).attr({
- "class": liclass,
- "rel": value
- });
- $(li).prepend(txt);
- $(aclose).attr({
- "class": "closebutton",
- "href": "#"
- });
-
- li.appendChild(aclose);
- holder.append(li);
-
- $(aclose).click(function() {
- removeItem($(this).parent("li"));
- return false;
- });
-
- if (!preadded) {
- $("#" + elemid + "_annoninput").remove();
- var _item;
- addInput(focusme);
- if (element.children("option[value=" + value + "]").length) {
- _item = element.children("option[value=" + value + "]");
- _item.get(0).setAttribute("selected", "selected");
- _item.attr("selected", "selected");
- if (!_item.hasClass("selected")) {
- _item.addClass("selected");
- }
- }
- else{
- var _item = $(document.createElement("option"));
- _item.attr("value", value).get(0).setAttribute("selected", "selected");
- _item.attr("value", value).attr("selected", "selected");
- _item.attr("value", value).addClass("selected");
- _item.text(title);
- element.append(_item);
- }
- if (options.onselect) {
- funCall(options.onselect, _item)
- }
- element.change();
- }
- holder.children("li.bit-box.deleted").removeClass("deleted");
- feed.hide();
- }
-
- function removeItem(item) {
-
- if (!item.hasClass('locked')) {
- item.fadeOut("fast");
- if (options.onremove) {
- var _item = element.children("option[value=" + item.attr("rel") + "]");
- funCall(options.onremove, _item)
- }
- element.children('option[value="' + item.attr("rel") + '"]').removeAttr("selected").removeClass("selected");
- item.remove();
- element.change();
- deleting = 0;
- }
- }
-
- function addInput(focusme) {
- var li = $(document.createElement("li"));
- var input = $(document.createElement("input"));
- var getBoxTimeout = 0;
-
- li.attr({
- "class": "bit-input",
- "id": elemid + "_annoninput"
- });
- input.attr({
- "type": "text",
- "class": "maininput",
- "size": "1"
- });
- holder.append(li.append(input));
-
- input.focus(function() {
- complete.fadeIn("fast");
- });
-
- input.blur(function() {
- if (options.complete_hover) {
- complete.fadeOut("fast");
- }
- else {
- input.focus();
- }
- });
-
- holder.click(function() {
- input.focus();
- if (feed.length && input.val().length) {
- feed.show();
- }
- else{
- feed.hide();
- complete.children(".default").show();
- }
- });
-
- input.keypress(function(event) {
- if (event.keyCode == 13) {
- return false;
- }
- //auto expand input
- input.attr("size", input.val().length + 1);
- });
-
- input.keydown(function(event) {
- //prevent to enter some bad chars when input is empty
- if (event.keyCode == 191) {
- event.preventDefault();
- return false;
- }
- });
-
- input.keyup(function(event) {
- var etext = xssPrevent(input.val());
-
- if (event.keyCode == 8 && etext.length == 0) {
- feed.hide();
- if (!holder.children("li.bit-box:last").hasClass('locked')) {
- if (holder.children("li.bit-box.deleted").length == 0) {
- holder.children("li.bit-box:last").addClass("deleted");
- return false;
- }
- else{
- if (deleting) {
- return;
- }
- deleting = 1;
- holder.children("li.bit-box.deleted").fadeOut("fast",
- function() {
- removeItem($(this));
- return false;
- });
- }
- }
- }
-
- if (event.keyCode != 40 && event.keyCode != 38 && event.keyCode!=37 && event.keyCode!=39 && etext.length != 0) {
- counter = 0;
-
- if (options.json_url) {
- if (options.cache && json_cache) {
- addMembers(etext);
- bindEvents();
- }
- else{
- getBoxTimeout++;
- var getBoxTimeoutValue = getBoxTimeout;
- setTimeout(function() {
- if (getBoxTimeoutValue != getBoxTimeout) return;
- $.getJSON(options.json_url, {tag: etext},
- function(data) {
- addMembers(etext, data);
- json_cache = true;
- bindEvents();
- });
- },
- options.delay);
- }
- }
- else{
- addMembers(etext);
- bindEvents();
- }
- complete.children(".default").hide();
- feed.show();
- }
- });
- if (focusme) {
- setTimeout(function() {
- input.focus();
- complete.children(".default").show();
- },
- 1);
- }
- }
-
- function addMembers(etext, data) {
- feed.html('');
-
- if (!options.cache && data != null) {
- cache = new Array();
- search_string = "";
- }
-
- addTextItem(etext);
-
- if (data != null && data.length) {
- $.each(data,
- function(i, val) {
- cache.push({
- key: val.key,
- value: val.value
- });
- search_string += "" + (cache.length - 1) + ":" + val.key + ";";
- });
- }
-
- var maximum = options.maxshownitems < cache.length ? options.maxshownitems: cache.length;
- var filter = "i";
- if (options.filter_case) {
- filter = "";
- }
-
- var myregexp,
- match;
- try{
- myregexp = eval('/(?:^|;)\\s*(\\d+)\\s*:[^;]*?' + etext + '[^;]*/g' + filter);
- match = myregexp.exec(search_string);
- }
- catch(ex) {
- };
-
- var content = '';
- while (match != null && maximum > 0) {
- var id = match[1];
- var object = cache[id];
- if (options.filter_selected && element.children("option[value=" + object.value + "]").hasClass("selected")) {
- //nothing here...
- }
- else{
- content += '<li rel="' + object.value + '">' + itemIllumination(object.key, etext) + '</li>';
- counter++;
- maximum--;
- }
- match = myregexp.exec(search_string);
- }
- feed.append(content);
-
- if (options.firstselected) {
- focuson = feed.children("li:visible:first");
- focuson.addClass("auto-focus");
- }
-
- if (counter > options.height) {
- feed.css({
- "height": (options.height * 24) + "px",
- "overflow": "auto"
- });
- }
- else{
- feed.css("height", "auto");
- }
- }
-
- function itemIllumination(text, etext) {
- if (options.filter_case) {
- try{
- eval("var text = text.replace(/(.*)(" + etext + ")(.*)/gi,'$1<em>$2</em>$3');");
- }
- catch(ex) {
- };
- }
- else{
- try{
- eval("var text = text.replace(/(.*)(" + etext.toLowerCase() + ")(.*)/gi,'$1<em>$2</em>$3');");
- }
- catch(ex) {
- };
- }
- return text;
- }
-
- function bindFeedEvent() {
- feed.children("li").mouseover(function() {
- feed.children("li").removeClass("auto-focus");
- $(this).addClass("auto-focus");
- focuson = $(this);
- });
-
- feed.children("li").mouseout(function() {
- $(this).removeClass("auto-focus");
- focuson = null;
- });
- }
-
- function removeFeedEvent() {
- feed.children("li").unbind("mouseover");
- feed.children("li").unbind("mouseout");
- feed.mousemove(function() {
- bindFeedEvent();
- feed.unbind("mousemove");
- });
- }
-
- function bindEvents() {
- var maininput = $("#" + elemid + "_annoninput").children(".maininput");
- bindFeedEvent();
- feed.children("li").unbind("mousedown");
- feed.children("li").mousedown(function() {
- var option = $(this);
- addItem(option.text(), option.attr("rel"), 0, 0, 1);
- feed.hide();
- complete.hide();
- });
-
- maininput.unbind("keydown");
- maininput.keydown(function(event) {
- if (event.keyCode == 191) {
- event.preventDefault();
- return false;
- }
-
- if (event.keyCode != 8) {
- holder.children("li.bit-box.deleted").removeClass("deleted");
- }
-
- if ((event.keyCode == 13 || event.keyCode == 9) && checkFocusOn()) {
- var option = focuson;
- addItem(option.text(), option.attr("rel"), 0, 0, 1);
- complete.hide();
- event.preventDefault();
- focuson = null;
- return false;
- }
-
- if ((event.keyCode == 13 || event.keyCode == 9) && !checkFocusOn()) {
- if (options.newel) {
- var value = xssPrevent($(this).val());
- addItem(value, value, 0, 0, 1);
- complete.hide();
- event.preventDefault();
- focuson = null;
- return false;
- }
-
- if (options.addontab) {
- focuson = feed.children("li:visible:first");
- var option = focuson;
- addItem(option.text(), option.attr("rel"), 0, 0, 1);
- complete.hide();
- event.preventDefault();
- focuson = null;
- return false;
- }
- }
-
- if (event.keyCode == 40) {
- removeFeedEvent();
- if (focuson == null || focuson.length == 0) {
- focuson = feed.children("li:visible:first");
- feed.get(0).scrollTop = 0;
- }
- else{
- focuson.removeClass("auto-focus");
- focuson = focuson.nextAll("li:visible:first");
- var prev = parseInt(focuson.prevAll("li:visible").length, 10);
- var next = parseInt(focuson.nextAll("li:visible").length, 10);
- if ((prev > Math.round(options.height / 2) || next <= Math.round(options.height / 2)) && typeof(focuson.get(0)) != "undefined") {
- feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (prev - Math.round(options.height / 2));
- }
- }
- feed.children("li").removeClass("auto-focus");
- focuson.addClass("auto-focus");
- }
- if (event.keyCode == 38) {
- removeFeedEvent();
- if (focuson == null || focuson.length == 0) {
- focuson = feed.children("li:visible:last");
- feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (parseInt(feed.children("li:visible").length, 10) - Math.round(options.height / 2));
- }
- else{
- focuson.removeClass("auto-focus");
- focuson = focuson.prevAll("li:visible:first");
- var prev = parseInt(focuson.prevAll("li:visible").length, 10);
- var next = parseInt(focuson.nextAll("li:visible").length, 10);
- if ((next > Math.round(options.height / 2) || prev <= Math.round(options.height / 2)) && typeof(focuson.get(0)) != "undefined") {
- feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (prev - Math.round(options.height / 2));
- }
- }
- feed.children("li").removeClass("auto-focus");
- focuson.addClass("auto-focus");
- }
- });
- }
-
- function maxItems() {
- if (options.maxitems != 0) {
- if (holder.children("li.bit-box").length < options.maxitems) {
- return true;
- }
- else{
- return false;
- }
- }
- }
-
- function addTextItem(value) {
- if (options.newel && maxItems()) {
- feed.children("li[fckb=1]").remove();
- if (value.length == 0) {
- return;
- }
- var li = $(document.createElement("li"));
- li.attr({
- "rel": value,
- "fckb": "1"
- }).html(value);
- feed.prepend(li);
- counter++;
- }
- else{
- return;
- }
- }
-
- function funCall(func, item) {
- var _object = "";
- for (i = 0; i < item.get(0).attributes.length; i++) {
- if (item.get(0).attributes[i].nodeValue != null) {
- _object += "\"_" + item.get(0).attributes[i].nodeName + "\": \"" + item.get(0).attributes[i].nodeValue + "\",";
- }
- }
- _object = "{" + _object + " notinuse: 0}";
- func.call(func, _object);
- }
-
- function checkFocusOn() {
- if (focuson == null) {
- return false;
- }
- if (focuson.length == 0) {
- return false;
- }
- return true;
- }
-
- function xssPrevent(string) {
- string = string.replace(/[\"\'][\s]*javascript:(.*)[\"\']/g, "\"\"");
- string = string.replace(/script(.*)/g, "");
- string = string.replace(/eval\((.*)\)/g, "");
- string = string.replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '');
- return string;
- }
-
- var options = $.extend({
- json_url: null,
- cache: false,
- height: "10",
- newel: false,
- addontab: false,
- firstselected: false,
- filter_case: false,
- filter_selected: false,
- complete_text: "Start to type...",
- maxshownitems: 30,
- maxitems: 10,
- onselect: null,
- onremove: null,
- attachto: null,
- delay: 350
- },
- opt);
-
- //system variables
- var holder = null;
- var feed = null;
- var complete = null;
- var counter = 0;
- var cache = new Array();
- var json_cache = false;
- var search_string = "";
- var focuson = null;
- var deleting = 0;
- var complete_hover = 1;
-
- var element = $(this);
- var elemid = element.attr("id");
- init();
-
- return this;
- });
- };
-});
diff --git a/themes/default/js/plugins/jquery.tokeninput.js b/themes/default/js/plugins/jquery.tokeninput.js
new file mode 100644
index 000000000..0c8662b3b
--- /dev/null
+++ b/themes/default/js/plugins/jquery.tokeninput.js
@@ -0,0 +1,777 @@
+/**
+ 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
+ *
+ * Copyright (c) 2009 James Smith (http://loopj.com)
+ * Licensed jointly under the GPL and MIT licenses,
+ * choose which one suits your project best!
+ *
+ */
+
+(function ($) {
+// Default settings
+var DEFAULT_SETTINGS = {
+ hintText: "Type in a search term",
+ noResultsText: "No results",
+ searchingText: "Searching...",
+ newText: "(new)",
+ deleteText: "&times;",
+ searchDelay: 300,
+ minChars: 1,
+ tokenLimit: null,
+ jsonContainer: null,
+ method: "GET",
+ contentType: "json",
+ queryParam: "q",
+ tokenDelimiter: ",",
+ preventDuplicates: false,
+ prePopulate: null,
+ processPrePopulate: false,
+ animateDropdown: true,
+ onResult: null,
+ onAdd: null,
+ onDelete: null,
+ allowCreation: false
+};
+
+// 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"
+};
+
+// Input box position "enum"
+var POSITION = {
+ BEFORE: 0,
+ AFTER: 1,
+ END: 2
+};
+
+// Keys "enum"
+var KEY = {
+ BACKSPACE: 8,
+ TAB: 9,
+ ENTER: 13,
+ ESCAPE: 27,
+ SPACE: 32,
+ PAGE_UP: 33,
+ PAGE_DOWN: 34,
+ END: 35,
+ HOME: 36,
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40,
+ NUMPAD_ENTER: 108,
+ COMMA: 188
+};
+
+
+// Expose the .tokenInput function to jQuery as a plugin
+$.fn.tokenInput = function (url_or_data, options) {
+ var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
+
+ return this.each(function () {
+ new $.TokenList(this, url_or_data, settings);
+ });
+};
+
+
+// TokenList class for each input
+$.TokenList = function (input, url_or_data, settings) {
+ //
+ // Initialization
+ //
+
+ // Configure the data source
+ if(typeof(url_or_data) === "string") {
+ // Set the url to query against
+ settings.url = url_or_data;
+
+ // 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;
+ } else {
+ settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.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;
+ }
+
+
+ // Save the tokens
+ var saved_tokens = [];
+
+ // Keep track of the number of tokens in the list
+ var token_count = 0;
+
+ // Basic cache to save on db hits
+ var cache = new $.TokenList.Cache();
+
+ // Keep track of the timeout, old vals
+ var timeout;
+ var input_val;
+
+ // Create a new text input an attach keyup events
+ var input_box = $("<input type=\"text\" autocomplete=\"off\">")
+ .css({
+ outline: "none"
+ })
+ .focus(function () {
+ if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
+ show_dropdown_hint();
+ }
+ })
+ .blur(function () {
+ hide_dropdown();
+ })
+ .bind("keyup keydown blur update", resize_input)
+ .keydown(function (event) {
+ var previous_token;
+ var next_token;
+
+ switch(event.keyCode) {
+ case KEY.LEFT:
+ case KEY.RIGHT:
+ case KEY.UP:
+ case KEY.DOWN:
+ if(!$(this).val()) {
+ previous_token = input_token.prev();
+ next_token = input_token.next();
+
+ if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
+ // Check if there is a previous/next token and it is selected
+ if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
+ deselect_token($(selected_token), POSITION.BEFORE);
+ } else {
+ deselect_token($(selected_token), POSITION.AFTER);
+ }
+ } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
+ // We are moving left, select the previous token if it exists
+ select_token($(previous_token.get(0)));
+ } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
+ // We are moving right, select the next token if it exists
+ select_token($(next_token.get(0)));
+ }
+ } else {
+ var dropdown_item = null;
+
+ if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
+ dropdown_item = $(selected_dropdown_item).next();
+ } else {
+ dropdown_item = $(selected_dropdown_item).prev();
+ }
+
+ if(dropdown_item.length) {
+ select_dropdown_item(dropdown_item);
+ }
+ return false;
+ }
+ break;
+
+ case KEY.BACKSPACE:
+ previous_token = input_token.prev();
+
+ if(!$(this).val().length) {
+ if(selected_token) {
+ delete_token($(selected_token));
+ } else if(previous_token.length) {
+ select_token($(previous_token.get(0)));
+ }
+
+ return false;
+ } else if($(this).val().length === 1) {
+ hide_dropdown();
+ } else {
+ // set a timeout just long enough to let this function finish.
+ setTimeout(function(){do_search();}, 5);
+ }
+ break;
+
+ case KEY.TAB:
+ case KEY.ENTER:
+ case KEY.NUMPAD_ENTER:
+ case KEY.COMMA:
+ if(selected_dropdown_item) {
+ add_token($(selected_dropdown_item));
+ return false;
+ }
+ break;
+
+ case KEY.ESCAPE:
+ hide_dropdown();
+ return true;
+
+ default:
+ if(String.fromCharCode(event.which)) {
+ // set a timeout just long enough to let this function finish.
+ setTimeout(function(){do_search();}, 5);
+ }
+ break;
+ }
+ });
+
+ 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);
+ } 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 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)
+ .click(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
+ toggle_select_token(li);
+ } else {
+ // Deselect selected token
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.END);
+ }
+
+ // Focus input box
+ input_box.focus();
+ }
+ })
+ .mouseover(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && selected_token !== this) {
+ li.addClass(settings.classes.highlightedToken);
+ }
+ })
+ .mouseout(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && selected_token !== this) {
+ li.removeClass(settings.classes.highlightedToken);
+ }
+ })
+ .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>")
+ .addClass(settings.classes.dropdown)
+ .appendTo("body")
+ .hide();
+
+ // 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"
+ });
+
+ // 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.id, value.name);
+ });
+ }
+
+ // Pre-populate from SELECT options
+ if ($(input).get(0).tagName == 'SELECT') {
+ $(input).children('option').each(function () {
+ insert_token($(this).attr('value'), $(this).text());
+ });
+ }
+
+
+
+ //
+ // Private functions
+ //
+
+ function resize_input() {
+ if(input_val === (input_val = input_box.val())) {return;}
+
+ // 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);
+ }
+
+ 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>" + settings.deleteText + "</span>")
+ .addClass(settings.classes.tokenDelete)
+ .appendTo(this_token)
+ .click(function () {
+ delete_token($(this).parent());
+ return false;
+ });
+
+ // Store data on the token
+ var token_data = {"id": id, "name": value};
+ $.data(this_token.get(0), "tokeninput", token_data);
+
+ // 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));
+
+ token_count += 1;
+
+ 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;
+
+ // 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 === li_data.id) {
+ found_existing_token = existing_token;
+ return false;
+ }
+ });
+
+ if(found_existing_token) {
+ select_token(found_existing_token);
+ input_token.insertAfter(found_existing_token);
+ input_box.focus();
+ return;
+ }
+ }
+
+ // Insert the new tokens
+ insert_token(li_data.id, li_data.name);
+
+ // Check the token limit
+ if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
+ input_box.hide();
+ hide_dropdown();
+ return;
+ } else {
+ input_box.focus();
+ }
+
+ // Clear input box
+ input_box.val("");
+
+ // Don't show the help dropdown, they've got the idea
+ hide_dropdown();
+
+ // Execute the onAdd callback if defined
+ if($.isFunction(callback)) {
+ callback.call(hidden_input,li_data);
+ }
+ }
+
+ // Select a token in the token list
+ function select_token (token) {
+ token.addClass(settings.classes.selectedToken);
+ selected_token = token.get(0);
+
+ // Hide input box
+ input_box.val("");
+
+ // 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);
+ selected_token = null;
+
+ if(position === POSITION.BEFORE) {
+ input_token.insertBefore(token);
+ 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
+ input_box.focus();
+ }
+
+ // Toggle selection of a token in the token list
+ 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 {
+ select_token(token);
+ }
+ }
+
+ // Delete a token from the token list
+ 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();
+ selected_token = null;
+
+ // Show the input box and give it focus again
+ input_box.focus();
+
+ // 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
+ var token_ids = $.map(saved_tokens, function (el) {
+ return el.id;
+ });
+ hidden_input.val(token_ids.join(settings.tokenDelimiter));
+
+ 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);
+ }
+ }
+
+ // Hide and clear the results dropdown
+ function hide_dropdown () {
+ dropdown.hide().empty();
+ 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>");
+ }
+
+ // Populate the results dropdown with some results
+ function populate_dropdown (query, results) {
+ if(results && results.length) {
+ dropdown.empty();
+ var dropdown_ul = $("<ul>")
+ .appendTo(dropdown)
+ .mouseover(function (event) {
+ select_dropdown_item($(event.target).closest("li"));
+ })
+ .mousedown(function (event) {
+ add_token($(event.target).closest("li"));
+ return false;
+ })
+ .hide();
+
+ $.each(results, function(index, value) {
+ var this_li = $("<li>" + highlight_term(value.name, query) + "</li>")
+ .appendTo(dropdown_ul);
+
+ if(index % 2) {
+ this_li.addClass(settings.classes.dropdownItem);
+ } else {
+ this_li.addClass(settings.classes.dropdownItem2);
+ }
+
+ if(index === 0) {
+ select_dropdown_item(this_li);
+ }
+
+ $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name});
+ });
+
+ show_dropdown();
+
+ if(settings.animateDropdown) {
+ dropdown_ul.slideDown("fast");
+ } else {
+ dropdown_ul.show();
+ }
+ } else {
+ if(settings.noResultsText) {
+ dropdown.html("<p>"+settings.noResultsText+"</p>");
+ show_dropdown();
+ }
+ }
+ }
+
+ // Highlight an item in the results dropdown
+ function select_dropdown_item (item) {
+ if(item) {
+ if(selected_dropdown_item) {
+ deselect_dropdown_item($(selected_dropdown_item));
+ }
+
+ item.addClass(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);
+ selected_dropdown_item = null;
+ }
+
+ // Do a search and show the "searching" dropdown if the input is longer
+ // than settings.minChars
+ function do_search() {
+ var query = input_box.val().toLowerCase();
+
+ if(query && query.length) {
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.AFTER);
+ }
+
+ if(query.length >= settings.minChars) {
+ show_dropdown_searching();
+ clearTimeout(timeout);
+
+ timeout = setTimeout(function(){
+ run_search(query);
+ }, settings.searchDelay);
+ } else {
+ hide_dropdown();
+ }
+ }
+ }
+
+ // Do the actual search
+ function run_search(query) {
+ var cached_results = cache.get(query);
+ if(cached_results) {
+ populate_dropdown(query, cached_results);
+ } else {
+ // Are we doing an ajax search or local data search?
+ if(settings.url) {
+ // Extract exisiting get params
+ var ajax_params = {};
+ ajax_params.data = {};
+ if(settings.url.indexOf("?") > -1) {
+ var parts = settings.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 = settings.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);
+ }
+
+ if(settings.allowCreation) {
+ results.push({name: input_box.val() + 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);
+ }
+ };
+
+ // 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.name.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()});
+ }
+
+ cache.add(query, results);
+
+ populate_dropdown(query, results);
+ }
+ }
+ }
+
+ if ($(input).get(0).tagName == 'SELECT') {
+ $(input).remove();
+ }
+};
+
+// Really basic cache for the results
+$.TokenList.Cache = function (options) {
+ var settings = $.extend({
+ max_size: 500
+ }, options);
+
+ var data = {};
+ var size = 0;
+
+ var flush = function () {
+ data = {};
+ size = 0;
+ };
+
+ this.add = function (query, results) {
+ if(size > settings.max_size) {
+ flush();
+ }
+
+ if(!data[query]) {
+ size += 1;
+ }
+
+ data[query] = results;
+ };
+
+ this.get = function (query) {
+ return data[query];
+ };
+};
+}(jQuery));