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:
parent
3f1951d443
commit
8b63dd982f
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(/"/g, '"'));
|
|
||||||
else if (field.data("pre"))
|
|
||||||
pre = JSON.parse((field.attr("data-pre") || "").replace(/"/g, '"'));
|
pre = JSON.parse((field.attr("data-pre") || "").replace(/"/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")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue