Add support for customizing max number of entries in EmailsField,

update to version 1.6 of jquery.tokeninput.js and fix problem with
reloading EmailsField pages by adding a data-pre attribute instead of
hacking the value attribute
 - Legacy-Id: 8264
This commit is contained in:
Ole Laursen 2014-08-13 08:58:10 +00:00
parent 3f1951d443
commit 8b63dd982f
3 changed files with 116 additions and 44 deletions

View file

@ -24,19 +24,25 @@ class EmailsField(forms.CharField):
kwargs["max_length"] = 1000 kwargs["max_length"] = 1000
if not "help_text" in kwargs: if not "help_text" in kwargs:
kwargs["help_text"] = "Type in name to search for person" kwargs["help_text"] = "Type in name to search for person"
max_entries = kwargs.pop("max_entries", None)
super(EmailsField, self).__init__(*args, **kwargs) super(EmailsField, self).__init__(*args, **kwargs)
self.widget.attrs["class"] = "tokenized-field" self.widget.attrs["class"] = "tokenized-field"
self.widget.attrs["data-ajax-url"] = lazy(urlreverse, str)("ajax_search_emails") # make this lazy to prevent initialization problem self.widget.attrs["data-ajax-url"] = lazy(urlreverse, str)("ajax_search_emails") # make this lazy to prevent initialization problem
if max_entries != None:
self.widget.attrs["data-max-entries"] = max_entries
def parse_tokenized_value(self, value): def parse_tokenized_value(self, value):
return Email.objects.filter(address__in=[x.strip() for x in value.split(",") if x.strip()]).select_related("person") return Email.objects.filter(address__in=[x.strip() for x in value.split(",") if x.strip()]).select_related("person")
def prepare_value(self, value): def prepare_value(self, value):
if not value: if not value:
return "" value = ""
if isinstance(value, str) or isinstance(value, unicode): if isinstance(value, basestring):
value = self.parse_tokenized_value(value) value = self.parse_tokenized_value(value)
return json_emails(value)
self.widget.attrs["data-pre"] = json_emails(value)
return ",".join(e.address for e in value)
def clean(self, value): def clean(self, value):
value = super(EmailsField, self).clean(value) value = super(EmailsField, self).clean(value)

View file

@ -1,6 +1,6 @@
/* /*
* jQuery Plugin: Tokenizing Autocomplete Text Entry * jQuery Plugin: Tokenizing Autocomplete Text Entry
* Version 1.5.0 * Version 1.6.0
* *
* Copyright (c) 2009 James Smith (http://loopj.com) * Copyright (c) 2009 James Smith (http://loopj.com)
* Licensed jointly under the GPL and MIT licenses, * Licensed jointly under the GPL and MIT licenses,
@ -11,26 +11,46 @@
(function ($) { (function ($) {
// Default settings // Default settings
var 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", hintText: "Type in a search term",
noResultsText: "No results", noResultsText: "No results",
searchingText: "Searching...", searchingText: "Searching...",
deleteText: "×", deleteText: "×",
searchDelay: 300, animateDropdown: true,
minChars: 1,
// Tokenization settings
tokenLimit: null, tokenLimit: null,
jsonContainer: null,
method: "GET",
contentType: "json",
queryParam: "q",
tokenDelimiter: ",", tokenDelimiter: ",",
preventDuplicates: false, preventDuplicates: false,
// Output settings
tokenValue: "id",
// Prepopulation settings
prePopulate: null, prePopulate: null,
processPrePopulate: false, processPrePopulate: false,
animateDropdown: true,
// 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, onResult: null,
onAdd: null, onAdd: null,
onDelete: null, onDelete: null,
idPrefix: "token-input-" onReady: null
}; };
// Default classes to use when theming // Default classes to use when theming
@ -93,7 +113,10 @@ var methods = {
remove: function(item) { remove: function(item) {
this.data("tokenInputObject").remove(item); this.data("tokenInputObject").remove(item);
return this; return this;
} },
get: function() {
return this.data("tokenInputObject").getTokens();
}
} }
// Expose the .tokenInput function to jQuery as a plugin // Expose the .tokenInput function to jQuery as a plugin
@ -113,16 +136,19 @@ $.TokenList = function (input, url_or_data, settings) {
// //
// Configure the data source // Configure the data source
if(typeof(url_or_data) === "string") { if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
// Set the url to query against // Set the url to query against
settings.url = url_or_data; settings.url = url_or_data;
// If the URL is a function, evaluate it here to do our initalization work
var url = computeURL();
// Make a smart guess about cross-domain if it wasn't explicitly specified // Make a smart guess about cross-domain if it wasn't explicitly specified
if(settings.crossDomain === undefined) { if(settings.crossDomain === undefined) {
if(settings.url.indexOf("://") === -1) { if(url.indexOf("://") === -1) {
settings.crossDomain = false; settings.crossDomain = false;
} else { } 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(typeof(url_or_data) === "object") { } else if(typeof(url_or_data) === "object") {
@ -223,6 +249,7 @@ $.TokenList = function (input, url_or_data, settings) {
if(!$(this).val().length) { if(!$(this).val().length) {
if(selected_token) { if(selected_token) {
delete_token($(selected_token)); delete_token($(selected_token));
hidden_input.change();
} else if(previous_token.length) { } else if(previous_token.length) {
select_token($(previous_token.get(0))); select_token($(previous_token.get(0)));
} }
@ -242,6 +269,7 @@ $.TokenList = function (input, url_or_data, settings) {
case KEY.COMMA: case KEY.COMMA:
if(selected_dropdown_item) { if(selected_dropdown_item) {
add_token($(selected_dropdown_item).data("tokeninput")); add_token($(selected_dropdown_item).data("tokeninput"));
hidden_input.change();
return false; return false;
} }
break; break;
@ -346,6 +374,10 @@ $.TokenList = function (input, url_or_data, settings) {
}); });
} }
// Initialization is done
if($.isFunction(settings.onReady)) {
settings.onReady.call();
}
// //
// Public functions // Public functions
@ -380,6 +412,10 @@ $.TokenList = function (input, url_or_data, settings) {
} }
}); });
} }
this.getTokens = function() {
return saved_tokens;
}
// //
// Private functions // Private functions
@ -390,8 +426,6 @@ $.TokenList = function (input, url_or_data, settings) {
input_box.hide(); input_box.hide();
hide_dropdown(); hide_dropdown();
return; return;
} else {
input_box.focus();
} }
} }
@ -413,7 +447,8 @@ $.TokenList = function (input, url_or_data, settings) {
// Inner function to a token to the list // Inner function to a token to the list
function insert_token(item) { function insert_token(item) {
var this_token = $("<li><p>"+ item.name +"</p></li>") var this_token = settings.tokenFormatter(item);
this_token = $(this_token)
.addClass(settings.classes.token) .addClass(settings.classes.token)
.insertBefore(input_token); .insertBefore(input_token);
@ -423,11 +458,13 @@ $.TokenList = function (input, url_or_data, settings) {
.appendTo(this_token) .appendTo(this_token)
.click(function () { .click(function () {
delete_token($(this).parent()); delete_token($(this).parent());
hidden_input.change();
return false; return false;
}); });
// Store data on the token // Store data on the token
var token_data = {"id": item.id, "name": item.name}; var token_data = {"id": item.id};
token_data[settings.propertyToSearch] = item[settings.propertyToSearch];
$.data(this_token.get(0), "tokeninput", item); $.data(this_token.get(0), "tokeninput", item);
// Save this token for duplicate checking // Save this token for duplicate checking
@ -435,13 +472,16 @@ $.TokenList = function (input, url_or_data, settings) {
selected_token_index++; selected_token_index++;
// Update the hidden input // Update the hidden input
var token_ids = $.map(saved_tokens, function (el) { update_hidden_input(saved_tokens, hidden_input);
return el.id;
});
hidden_input.val(token_ids.join(settings.tokenDelimiter));
token_count += 1; token_count += 1;
// Check the token limit
if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
input_box.hide();
hide_dropdown();
}
return this_token; return this_token;
} }
@ -470,8 +510,10 @@ $.TokenList = function (input, url_or_data, settings) {
} }
// Insert the new tokens // Insert the new tokens
insert_token(item); if(settings.tokenLimit == null || token_count < settings.tokenLimit) {
checkTokenLimit(); insert_token(item);
checkTokenLimit();
}
// Clear input box // Clear input box
input_box.val(""); input_box.val("");
@ -553,10 +595,7 @@ $.TokenList = function (input, url_or_data, settings) {
if(index < selected_token_index) selected_token_index--; if(index < selected_token_index) selected_token_index--;
// Update the hidden input // Update the hidden input
var token_ids = $.map(saved_tokens, function (el) { update_hidden_input(saved_tokens, hidden_input);
return el.id;
});
hidden_input.val(token_ids.join(settings.tokenDelimiter));
token_count -= 1; token_count -= 1;
@ -573,6 +612,15 @@ $.TokenList = function (input, url_or_data, settings) {
} }
} }
// Update the hidden input box value
function update_hidden_input(saved_tokens, hidden_input) {
var token_values = $.map(saved_tokens, function (el) {
return el[settings.tokenValue];
});
hidden_input.val(token_values.join(settings.tokenDelimiter));
}
// Hide and clear the results dropdown // Hide and clear the results dropdown
function hide_dropdown () { function hide_dropdown () {
dropdown.hide().empty(); dropdown.hide().empty();
@ -608,6 +656,10 @@ $.TokenList = function (input, url_or_data, settings) {
function highlight_term(value, term) { function highlight_term(value, term) {
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>"); 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 // Populate the results dropdown with some results
function populate_dropdown (query, results) { function populate_dropdown (query, results) {
@ -620,14 +672,18 @@ $.TokenList = function (input, url_or_data, settings) {
}) })
.mousedown(function (event) { .mousedown(function (event) {
add_token($(event.target).closest("li").data("tokeninput")); add_token($(event.target).closest("li").data("tokeninput"));
hidden_input.change();
return false; return false;
}) })
.hide(); .hide();
$.each(results, function(index, value) { $.each(results, function(index, value) {
var this_li = $("<li>" + highlight_term(value.name, query) + "</li>") var this_li = settings.resultsFormatter(value);
.appendTo(dropdown_ul);
this_li = find_value_and_highlight_term(this_li ,value[settings.propertyToSearch], query);
this_li = $(this_li).appendTo(dropdown_ul);
if(index % 2) { if(index % 2) {
this_li.addClass(settings.classes.dropdownItem); this_li.addClass(settings.classes.dropdownItem);
} else { } else {
@ -699,17 +755,19 @@ $.TokenList = function (input, url_or_data, settings) {
// Do the actual search // Do the actual search
function run_search(query) { function run_search(query) {
var cached_results = cache.get(query); var cache_key = query + computeURL();
var cached_results = cache.get(cache_key);
if(cached_results) { if(cached_results) {
populate_dropdown(query, cached_results); populate_dropdown(query, cached_results);
} else { } else {
// Are we doing an ajax search or local data search? // Are we doing an ajax search or local data search?
if(settings.url) { if(settings.url) {
var url = computeURL();
// Extract exisiting get params // Extract exisiting get params
var ajax_params = {}; var ajax_params = {};
ajax_params.data = {}; ajax_params.data = {};
if(settings.url.indexOf("?") > -1) { if(url.indexOf("?") > -1) {
var parts = settings.url.split("?"); var parts = url.split("?");
ajax_params.url = parts[0]; ajax_params.url = parts[0];
var param_array = parts[1].split("&"); var param_array = parts[1].split("&");
@ -718,7 +776,7 @@ $.TokenList = function (input, url_or_data, settings) {
ajax_params.data[kv[0]] = kv[1]; ajax_params.data[kv[0]] = kv[1];
}); });
} else { } else {
ajax_params.url = settings.url; ajax_params.url = url;
} }
// Prepare the request // Prepare the request
@ -734,7 +792,7 @@ $.TokenList = function (input, url_or_data, settings) {
if($.isFunction(settings.onResult)) { if($.isFunction(settings.onResult)) {
results = settings.onResult.call(hidden_input, 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 // only populate the dropdown if the results are associated with the active search query
if(input_box.val().toLowerCase() === query) { if(input_box.val().toLowerCase() === query) {
@ -747,17 +805,26 @@ $.TokenList = function (input, url_or_data, settings) {
} else if(settings.local_data) { } else if(settings.local_data) {
// Do the search through local data // Do the search through local data
var results = $.grep(settings.local_data, function (row) { 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)) { if($.isFunction(settings.onResult)) {
results = settings.onResult.call(hidden_input, results); results = settings.onResult.call(hidden_input, results);
} }
cache.add(query, results); cache.add(cache_key, results);
populate_dropdown(query, 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 // Really basic cache for the results

View file

@ -3,15 +3,14 @@ function setupTokenizedField(field) {
return; // don't tokenize hidden template snippets return; // don't tokenize hidden template snippets
var pre = []; var pre = [];
if (field.val()) if (field.attr("data-pre"))
pre = JSON.parse((field.val() || "").replace(/&quot;/g, '"'));
else if (field.data("pre"))
pre = JSON.parse((field.attr("data-pre") || "").replace(/&quot;/g, '"')); pre = JSON.parse((field.attr("data-pre") || "").replace(/&quot;/g, '"'));
field.tokenInput(field.attr("data-ajax-url"), { field.tokenInput(field.attr("data-ajax-url"), {
hintText: "", hintText: "",
preventDuplicates: true, preventDuplicates: true,
prePopulate: pre prePopulate: pre,
tokenLimit: field.attr("data-max-entries")
}); });
} }