diff --git a/.gitignore b/.gitignore index 04c4fef1..1091cbcf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ _site _layouts -.DS_Store \ No newline at end of file +.DS_Store +*~ +\#*# diff --git a/README.md b/README.md index 7642be82..963e1ecc 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,24 @@ Overview -------- Tokeninput is a jQuery plugin which allows your users to select multiple items from a predefined list, using autocompletion as they type to find each item. You may have seen a similar type of text entry when filling in the recipients field sending messages on facebook. -Documentation, Features and Demos ---------------------------------- -Full details and documentation can be found on the project page here: +Changes from original jquery-tokeninput +--------------------------------------- + +This is a forked version of jquery-tokeninput blending the latest versions of vdepizzol's and loopj's versions, plus updating the search-by-function behavior to actually work and to be similar to JQuery UI Autocomplete's behavior. + +In this version, in addition to having all of the latest features of both loopj's 1.5 version and vdepizzol's improvements, the main change is that in addition to providing a URL or inline data for the first parameter, you can provide a search function to handle the lookups for you. This function can construct whatever type of request you like, such as: + + $("#element").tokenInput( + function(query, response) { + $.ajax({ url: "your url here", + data: { q: query, extra: "stuff" }, + dataType: "json", + success: function(data) { response(data) } + }); + }, + { + preventDuplicates: true, + onAdd: function(list,item) { alert("You added " + item.name) } + }); + - \ No newline at end of file diff --git a/demo.html b/demo.html index 1d87fc39..c7cd77c7 100644 --- a/demo.html +++ b/demo.html @@ -1,5 +1,7 @@ + + @@ -69,6 +71,21 @@

Facebook Theme

+

Facebook Theme & Sortable

+
+ + + +
+ +

Custom Labels

@@ -212,8 +229,65 @@

Using onAdd and onDelete Callbacks

});
+ +

Allowing Custom Entry

+
+ + + +
+ +

Custom Result Formatter for JSON

+
+ + + +
- +

Using local data with parseName function and searchColumns setting

+
+ + + +
+

Using the add, remove and clear Methods

Add Token | Remove Token | Clear Tokens
@@ -244,4 +318,4 @@

Using the add, remove and clear Methods

- \ No newline at end of file + diff --git a/demo_tabindex.html b/demo_tabindex.html new file mode 100644 index 00000000..6c8050cb --- /dev/null +++ b/demo_tabindex.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + +

jQuery Tokeninput Demos

+ +

Tab index demo

+
+ + + + + + +
+ +

Tab index demo in a form

+
+
+ + + + + +
+ +
+ + diff --git a/src/jquery.tokeninput.js b/src/jquery.tokeninput.js index a767d842..a2d087f5 100644 --- a/src/jquery.tokeninput.js +++ b/src/jquery.tokeninput.js @@ -9,42 +9,73 @@ */ (function ($) { + // Default settings var DEFAULT_SETTINGS = { + + // Strings + hintText: "Type in a search term", noResultsText: "No results", searchingText: "Searching...", deleteText: "×", + + // Field exibition and behavior + + tokenLimit: null, + allowCustomEntry: false, + preventDuplicates: false, + searchColumns: ['name'], + parseName: null, + escapeHTML: true, searchDelay: 300, minChars: 1, - tokenLimit: null, - jsonContainer: null, + makeSortable: false, + animateDropdown: true, + + // Get local & external data + method: "GET", contentType: "json", queryParam: "q", - tokenDelimiter: ",", - preventDuplicates: false, + jsonContainer: null, prePopulate: null, processPrePopulate: false, - animateDropdown: true, + + // Submit input value + + tokenDelimiter: ",", + tokenQuote: "'", + tokenQuoteEscaped: "\\'", + tokensFormatter: null, + + // Callbacks + onResult: null, onAdd: null, onDelete: null, idPrefix: "token-input-" }; + // Default classes to use when theming var DEFAULT_CLASSES = { tokenList: "token-input-list", + tokenListFocused: "token-input-focus", token: "token-input-token", tokenDelete: "token-input-delete-token", selectedToken: "token-input-selected-token", highlightedToken: "token-input-highlighted-token", + draggedToken: "token-input-dragged-token", + draggedClone: "token-input-dragged-clone", dropdown: "token-input-dropdown", dropdownItem: "token-input-dropdown-item", dropdownItem2: "token-input-dropdown-item2", + dropdownHint: "token-input-hint", selectedDropdownItem: "token-input-selected-dropdown-item", - inputToken: "token-input-input-token" + inputToken: "token-input-input-token", + insertBefore: "token-input-insert-before", + insertAfter: "token-input-insert-after" }; // Input box position "enum" @@ -69,6 +100,7 @@ var KEY = { UP: 38, RIGHT: 39, DOWN: 40, + DELETE: 46, NUMPAD_ENTER: 108, COMMA: 188 }; @@ -107,15 +139,15 @@ $.fn.tokenInput = function (method) { }; // TokenList class for each input -$.TokenList = function (input, url_or_data, settings) { +$.TokenList = function (input, url_or_data_or_function, settings) { // // Initialization // // Configure the data source - if(typeof(url_or_data) === "string") { + if(typeof(url_or_data_or_function) === "string") { // Set the url to query against - settings.url = url_or_data; + settings.url = url_or_data_or_function; // Make a smart guess about cross-domain if it wasn't explicitly specified if(settings.crossDomain === undefined) { @@ -125,9 +157,11 @@ $.TokenList = function (input, url_or_data, settings) { settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]); } } - } else if(typeof(url_or_data) === "object") { + } else if(typeof(url_or_data_or_function) === "function") { + settings.sourceFunction = url_or_data_or_function; + } else if(typeof(url_or_data_or_function) === "object") { // Set the local data to search through - settings.local_data = url_or_data; + settings.local_data = url_or_data_or_function; } // Build class names @@ -146,7 +180,7 @@ $.TokenList = function (input, url_or_data, settings) { // Save the tokens - var saved_tokens = []; + var saved_tokens = {}; // Keep track of the number of tokens in the list var token_count = 0; @@ -168,8 +202,15 @@ $.TokenList = function (input, url_or_data, settings) { if (settings.tokenLimit === null || settings.tokenLimit !== token_count) { show_dropdown_hint(); } + if($(input_box).is(":visible")) { + token_list.addClass(settings.classes.tokenListFocused); + } }) .blur(function () { + if(selected_token) { + deselect_token($(selected_token)); + } + token_list.removeClass(settings.classes.tokenListFocused); hide_dropdown(); $(this).val(""); }) @@ -195,25 +236,69 @@ $.TokenList = function (input, url_or_data, settings) { deselect_token($(selected_token), POSITION.AFTER); } } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) { + if(selected_token) { + deselect_token($(selected_token)); + } // 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) { + if(selected_token) { + deselect_token($(selected_token)); + } // 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(); + + if(settings.allowCustomEntry == true) { + + if(event.keyCode === KEY.DOWN) { + if($(selected_dropdown_item).length) { + if($(selected_dropdown_item).next().length) { + dropdown_item = $(selected_dropdown_item).next(); + } else { + deselect_dropdown_item($(selected_dropdown_item)); + } + } else { + dropdown_item = $(dropdown).find('li:first-child'); + } + } else if(event.keyCode === KEY.UP) { + if($(selected_dropdown_item).length) { + if($(selected_dropdown_item).prev().length) { + dropdown_item = $(selected_dropdown_item).prev(); + } else { + deselect_dropdown_item($(selected_dropdown_item)); + } + } else { + dropdown_item = $(dropdown).find('li:last-child'); + } + } + + if(dropdown_item != null) { + select_dropdown_item(dropdown_item); + } + } else { - dropdown_item = $(selected_dropdown_item).prev(); + + if(event.keyCode === KEY.DOWN) { + dropdown_item = $(selected_dropdown_item).next(); + } else if(event.keyCode === KEY.UP) { + dropdown_item = $(selected_dropdown_item).prev(); + } + + if(dropdown_item && dropdown_item.length) { + select_dropdown_item(dropdown_item); + } + } - - if(dropdown_item.length) { - select_dropdown_item(dropdown_item); + + if(event.keyCode === KEY.LEFT || event.keyCode === KEY.RIGHT) { + // we need to allow caret moving here + return true; + } else { + return false; } - return false; } break; @@ -236,15 +321,39 @@ $.TokenList = function (input, url_or_data, settings) { } break; + case KEY.DELETE: + next_token = input_token.next(); + if(!$(this).val().length) { + if(selected_token) { + delete_token($(selected_token)); + } else if(next_token.length) { + select_token($(next_token.get(0))); + } + } + + break; + case KEY.TAB: case KEY.ENTER: case KEY.NUMPAD_ENTER: case KEY.COMMA: - if(selected_dropdown_item) { - add_token($(selected_dropdown_item).data("tokeninput")); + + if(event.keyCode == KEY.TAB && !$(input_box).val().length) { + hide_dropdown(); + // let the browser handle the tab key properly if user is trying to tab through or out + return true; + } + + if(selected_dropdown_item) { + add_token($(selected_dropdown_item)); + } + + if(settings.allowCustomEntry == true && $.trim($(input_box).val()) != '') { + add_token($(input_box).val()); + } + return false; - } - break; + break; case KEY.ESCAPE: hide_dropdown(); @@ -258,10 +367,18 @@ $.TokenList = function (input, url_or_data, settings) { break; } }); - + + var unique_counter = 0; + function get_unique_id() { + unique_counter++; + return 'u' + unique_counter; + } + + // hides original input box + input.type = 'hidden'; + // Keep a reference to the original input box var hidden_input = $(input) - .hide() .val("") .focus(function () { input_box.focus(); @@ -270,6 +387,9 @@ $.TokenList = function (input, url_or_data, settings) { input_box.blur(); }); + // Carry over the tab index if it's set + input_box.attr({ tabindex: hidden_input.attr('tabindex') }); + // Keep a reference to the selected token and dropdown item var selected_token = null; var selected_token_index = 0; @@ -277,10 +397,11 @@ $.TokenList = function (input, url_or_data, settings) { // The list to store the token items in var token_list = $("