Fixed messages. New styling for token input plugin.
This commit is contained in:
parent
c0c41a7752
commit
52e62f8ddb
14 changed files with 359 additions and 412 deletions
270
vendor/assets/javascripts/jquery.tokeninput.js
vendored
270
vendor/assets/javascripts/jquery.tokeninput.js
vendored
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* jQuery Plugin: Tokenizing Autocomplete Text Entry
|
||||
* Version 1.4.2
|
||||
* Version 1.6.0
|
||||
*
|
||||
* Copyright (c) 2009 James Smith (http://loopj.com)
|
||||
* Licensed jointly under the GPL and MIT licenses,
|
||||
|
|
@ -11,24 +11,46 @@
|
|||
(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: "×",
|
||||
searchDelay: 300,
|
||||
minChars: 1,
|
||||
animateDropdown: true,
|
||||
|
||||
// Tokenization settings
|
||||
tokenLimit: null,
|
||||
jsonContainer: null,
|
||||
method: "GET",
|
||||
contentType: "json",
|
||||
queryParam: "q",
|
||||
tokenDelimiter: ",",
|
||||
preventDuplicates: false,
|
||||
|
||||
// Output settings
|
||||
tokenValue: "id",
|
||||
|
||||
// Prepopulation settings
|
||||
prePopulate: null,
|
||||
animateDropdown: true,
|
||||
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
|
||||
onDelete: null,
|
||||
onReady: null
|
||||
};
|
||||
|
||||
// Default classes to use when theming
|
||||
|
|
@ -71,17 +93,42 @@ var KEY = {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
$.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) {
|
||||
//
|
||||
|
|
@ -89,19 +136,22 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
//
|
||||
|
||||
// Configure the data source
|
||||
if($.type(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;
|
||||
|
||||
// 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) {
|
||||
if(url.indexOf("://") === -1) {
|
||||
settings.crossDomain = false;
|
||||
} else {
|
||||
settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]);
|
||||
settings.crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
|
||||
}
|
||||
}
|
||||
} else if($.type(url_or_data) === "array") {
|
||||
} else if(typeof(url_or_data) === "object") {
|
||||
// Set the local data to search through
|
||||
settings.local_data = url_or_data;
|
||||
}
|
||||
|
|
@ -139,6 +189,7 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
.css({
|
||||
outline: "none"
|
||||
})
|
||||
.attr("id", settings.idPrefix + input.id)
|
||||
.focus(function () {
|
||||
if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
|
||||
show_dropdown_hint();
|
||||
|
|
@ -146,6 +197,7 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
})
|
||||
.blur(function () {
|
||||
hide_dropdown();
|
||||
$(this).val("");
|
||||
})
|
||||
.bind("keyup keydown blur update", resize_input)
|
||||
.keydown(function (event) {
|
||||
|
|
@ -197,6 +249,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)));
|
||||
}
|
||||
|
|
@ -215,7 +268,8 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
case KEY.NUMPAD_ENTER:
|
||||
case KEY.COMMA:
|
||||
if(selected_dropdown_item) {
|
||||
add_token($(selected_dropdown_item));
|
||||
add_token($(selected_dropdown_item).data("tokeninput"));
|
||||
hidden_input.change();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
|
@ -246,6 +300,7 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
|
||||
// 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
|
||||
|
|
@ -308,19 +363,72 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
|
||||
// Pre-populate list if items exist
|
||||
hidden_input.val("");
|
||||
li_data = settings.prePopulate || hidden_input.data("pre");
|
||||
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);
|
||||
insert_token(value);
|
||||
checkTokenLimit();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialization is done
|
||||
if($.isFunction(settings.onReady)) {
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
//
|
||||
|
||||
function checkTokenLimit() {
|
||||
if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
|
||||
input_box.hide();
|
||||
hide_dropdown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function resize_input() {
|
||||
if(input_val === (input_val = input_box.val())) {return;}
|
||||
|
||||
|
|
@ -338,8 +446,9 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
}
|
||||
|
||||
// Inner function to a token to the list
|
||||
function insert_token(id, value) {
|
||||
var this_token = $("<li><p>"+ value +"</p> </li>")
|
||||
function insert_token(item) {
|
||||
var this_token = settings.tokenFormatter(item);
|
||||
this_token = $(this_token)
|
||||
.addClass(settings.classes.token)
|
||||
.insertBefore(input_token);
|
||||
|
||||
|
|
@ -349,30 +458,35 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
.appendTo(this_token)
|
||||
.click(function () {
|
||||
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 = {"id": item.id};
|
||||
token_data[settings.propertyToSearch] = item[settings.propertyToSearch];
|
||||
$.data(this_token.get(0), "tokeninput", item);
|
||||
|
||||
// Save this token for duplicate checking
|
||||
saved_tokens.push(token_data);
|
||||
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;
|
||||
|
||||
// Check the token limit
|
||||
if(settings.tokenLimit !== null && token_count >= 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;
|
||||
|
||||
// See if the token already exists and select it if we don't want duplicates
|
||||
|
|
@ -381,7 +495,7 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
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.id === item.id) {
|
||||
found_existing_token = existing_token;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -396,15 +510,9 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
}
|
||||
|
||||
// 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();
|
||||
if(settings.tokenLimit == null || token_count < settings.tokenLimit) {
|
||||
insert_token(item);
|
||||
checkTokenLimit();
|
||||
}
|
||||
|
||||
// Clear input box
|
||||
|
|
@ -415,7 +523,7 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
|
||||
// Execute the onAdd callback if defined
|
||||
if($.isFunction(callback)) {
|
||||
callback(li_data);
|
||||
callback.call(hidden_input,item);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -438,10 +546,13 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
|
||||
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
|
||||
|
|
@ -469,6 +580,9 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
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;
|
||||
|
|
@ -477,15 +591,11 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
input_box.focus();
|
||||
|
||||
// Remove this token from the saved list
|
||||
saved_tokens = $.grep(saved_tokens, function (val) {
|
||||
return (val.id !== token_data.id);
|
||||
});
|
||||
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));
|
||||
update_hidden_input(saved_tokens, hidden_input);
|
||||
|
||||
token_count -= 1;
|
||||
|
||||
|
|
@ -498,10 +608,19 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
|
||||
// Execute the onDelete callback if defined
|
||||
if($.isFunction(callback)) {
|
||||
callback(token_data);
|
||||
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
|
||||
function hide_dropdown () {
|
||||
dropdown.hide().empty();
|
||||
|
|
@ -537,6 +656,10 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
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) {
|
||||
|
|
@ -548,15 +671,19 @@ $.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();
|
||||
|
||||
$.each(results, function(index, value) {
|
||||
var this_li = $("<li>" + highlight_term(value.name, query) + "</li>")
|
||||
.appendTo(dropdown_ul);
|
||||
|
||||
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 {
|
||||
|
|
@ -567,7 +694,7 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
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();
|
||||
|
|
@ -628,17 +755,19 @@ $.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) {
|
||||
populate_dropdown(query, cached_results);
|
||||
} else {
|
||||
// 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(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("&");
|
||||
|
|
@ -647,7 +776,7 @@ $.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
|
||||
|
|
@ -661,9 +790,9 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
// Attach the success callback
|
||||
ajax_params.success = function(results) {
|
||||
if($.isFunction(settings.onResult)) {
|
||||
results = settings.onResult.call(this, results);
|
||||
results = settings.onResult.call(hidden_input, results);
|
||||
}
|
||||
cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : 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) {
|
||||
|
|
@ -676,13 +805,26 @@ $.TokenList = function (input, url_or_data, settings) {
|
|||
} 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;
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue