mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
[gsoc2009/admin-ui] Some love for autocomplete.
- A new, nicer m2m autocomplete - Backend autocomplete view returns JSON instead of a funky parsed format - Upgraded the FKey autocomplete plugin git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/admin-ui@11795 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
379fecb829
commit
2ea3b4e058
110
django/contrib/admin/media/css/jquery.token.input.css
Normal file
110
django/contrib/admin/media/css/jquery.token.input.css
Normal file
@ -0,0 +1,110 @@
|
||||
ul.token-input-list {
|
||||
overflow: hidden;
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
width: 400px;
|
||||
border: 1px solid #999;
|
||||
cursor: text;
|
||||
font-size: 12px;
|
||||
font-family: Verdana;
|
||||
z-index: 999;
|
||||
margin: 0;
|
||||
padding: 0 !important;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
ul.token-input-list li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.token-input-list li input {
|
||||
border: 0;
|
||||
width: 350px;
|
||||
padding: 3px 8px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
li.token-input-token {
|
||||
overflow: hidden;
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
margin: 3px;
|
||||
padding: 3px 5px;
|
||||
background-color: #d0efa0;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
display: block;
|
||||
}
|
||||
|
||||
li.token-input-token p {
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li.token-input-token span {
|
||||
float: right;
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li.token-input-selected-token {
|
||||
background-color: #08844e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
li.token-input-selected-token span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
div.token-input-dropdown {
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
font-family: Verdana;
|
||||
z-index: 1;
|
||||
margin-left: 106px;
|
||||
}
|
||||
|
||||
div.token-input-dropdown p {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li {
|
||||
background-color: #fff;
|
||||
padding: 3px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-dropdown-item {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-dropdown-item2 {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li em {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-selected-dropdown-item {
|
||||
background-color: #d0efa0;
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
/*
|
||||
* Autocomplete - jQuery plugin 1.0.2
|
||||
* jQuery Autocomplete plugin 1.1
|
||||
*
|
||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
|
||||
* Copyright (c) 2009 Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
|
||||
*
|
||||
* Revision: $Id: jquery.autocomplete.js 14 2009-08-22 10:29:29Z joern.zaefferer $
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
@ -90,6 +89,9 @@ $.Autocompleter = function(input, options) {
|
||||
|
||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
|
||||
// a keypress means the input has focus
|
||||
// avoids issue where input had focus before the autocomplete was applied
|
||||
hasFocus = 1;
|
||||
// track last key pressed
|
||||
lastKeyPressCode = event.keyCode;
|
||||
switch(event.keyCode) {
|
||||
@ -209,7 +211,21 @@ $.Autocompleter = function(input, options) {
|
||||
if ( options.multiple ) {
|
||||
var words = trimWords($input.val());
|
||||
if ( words.length > 1 ) {
|
||||
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
|
||||
var seperator = options.multipleSeparator.length;
|
||||
var cursorAt = $(input).selection().start;
|
||||
var wordAt, progress = 0;
|
||||
$.each(words, function(i, word) {
|
||||
progress += word.length;
|
||||
if (cursorAt <= progress) {
|
||||
wordAt = i;
|
||||
return false;
|
||||
}
|
||||
progress += seperator;
|
||||
});
|
||||
words[wordAt] = v;
|
||||
// TODO this should set the cursor to the right position, but it gets overriden somewhere
|
||||
//$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
|
||||
v = words.join( options.multipleSeparator );
|
||||
}
|
||||
v += options.multipleSeparator;
|
||||
}
|
||||
@ -246,22 +262,27 @@ $.Autocompleter = function(input, options) {
|
||||
};
|
||||
|
||||
function trimWords(value) {
|
||||
if ( !value ) {
|
||||
if (!value)
|
||||
return [""];
|
||||
}
|
||||
var words = value.split( options.multipleSeparator );
|
||||
var result = [];
|
||||
$.each(words, function(i, value) {
|
||||
if ( $.trim(value) )
|
||||
result[i] = $.trim(value);
|
||||
if (!options.multiple)
|
||||
return [$.trim(value)];
|
||||
return $.map(value.split(options.multipleSeparator), function(word) {
|
||||
return $.trim(value).length ? $.trim(word) : null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function lastWord(value) {
|
||||
if ( !options.multiple )
|
||||
return value;
|
||||
var words = trimWords(value);
|
||||
if (words.length == 1)
|
||||
return words[0];
|
||||
var cursorAt = $(input).selection().start;
|
||||
if (cursorAt == value.length) {
|
||||
words = trimWords(value)
|
||||
} else {
|
||||
words = trimWords(value.replace(value.substring(cursorAt), ""));
|
||||
}
|
||||
return words[words.length - 1];
|
||||
}
|
||||
|
||||
@ -275,7 +296,7 @@ $.Autocompleter = function(input, options) {
|
||||
// fill in the value (keep the case the user has typed)
|
||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
|
||||
// select the portion of the value not typed by the user (so the next character will erase)
|
||||
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
|
||||
$(input).selection(previousValue.length, previousValue.length + sValue.length);
|
||||
}
|
||||
};
|
||||
|
||||
@ -299,15 +320,14 @@ $.Autocompleter = function(input, options) {
|
||||
var words = trimWords($input.val()).slice(0, -1);
|
||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
|
||||
}
|
||||
else
|
||||
else {
|
||||
$input.val( "" );
|
||||
$input.trigger("result", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (wasVisible)
|
||||
// position cursor at end of input field
|
||||
$.Autocompleter.Selection(input, input.value.length, input.value.length);
|
||||
};
|
||||
|
||||
function receiveData(q, data) {
|
||||
@ -421,6 +441,9 @@ $.Autocompleter.Cache = function(options) {
|
||||
if (!options.matchCase)
|
||||
s = s.toLowerCase();
|
||||
var i = s.indexOf(sub);
|
||||
if (options.matchContains == "word"){
|
||||
i = s.toLowerCase().search("\\b" + sub.toLowerCase());
|
||||
}
|
||||
if (i == -1) return false;
|
||||
return i == 0 || options.matchContains;
|
||||
};
|
||||
@ -738,22 +761,48 @@ $.Autocompleter.Select = function (options, input, select, config) {
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Selection = function(field, start, end) {
|
||||
if( field.createTextRange ){
|
||||
var selRange = field.createTextRange();
|
||||
selRange.collapse(true);
|
||||
selRange.moveStart("character", start);
|
||||
selRange.moveEnd("character", end);
|
||||
selRange.select();
|
||||
} else if( field.setSelectionRange ){
|
||||
field.setSelectionRange(start, end);
|
||||
} else {
|
||||
if( field.selectionStart ){
|
||||
field.selectionStart = start;
|
||||
field.selectionEnd = end;
|
||||
$.fn.selection = function(start, end) {
|
||||
if (start !== undefined) {
|
||||
return this.each(function() {
|
||||
if( this.createTextRange ){
|
||||
var selRange = this.createTextRange();
|
||||
if (end === undefined || start == end) {
|
||||
selRange.move("character", start);
|
||||
selRange.select();
|
||||
} else {
|
||||
selRange.collapse(true);
|
||||
selRange.moveStart("character", start);
|
||||
selRange.moveEnd("character", end);
|
||||
selRange.select();
|
||||
}
|
||||
} else if( this.setSelectionRange ){
|
||||
this.setSelectionRange(start, end);
|
||||
} else if( this.selectionStart ){
|
||||
this.selectionStart = start;
|
||||
this.selectionEnd = end;
|
||||
}
|
||||
});
|
||||
}
|
||||
var field = this[0];
|
||||
if ( field.createTextRange ) {
|
||||
var range = document.selection.createRange(),
|
||||
orig = field.value,
|
||||
teststring = "<->",
|
||||
textLength = range.text.length;
|
||||
range.text = teststring;
|
||||
var caretAt = field.value.indexOf(teststring);
|
||||
field.value = orig;
|
||||
this.selection(caretAt, caretAt + textLength);
|
||||
return {
|
||||
start: caretAt,
|
||||
end: caretAt + textLength
|
||||
}
|
||||
} else if( field.selectionStart !== undefined ){
|
||||
return {
|
||||
start: field.selectionStart,
|
||||
end: field.selectionEnd
|
||||
}
|
||||
}
|
||||
field.focus();
|
||||
};
|
||||
|
||||
})(jQuery);
|
597
django/contrib/admin/media/js/jquery.tokeninput.js
Normal file
597
django/contrib/admin/media/js/jquery.tokeninput.js
Normal file
@ -0,0 +1,597 @@
|
||||
/*
|
||||
* jQuery Plugin: Tokenizing Autocomplete Text Entry
|
||||
* Version 1.1
|
||||
*
|
||||
* Copyright (c) 2009 James Smith (http://loopj.com)
|
||||
* Licensed jointly under the GPL and MIT licenses,
|
||||
* choose which one suits your project best!
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
|
||||
$.fn.tokenInput = function (url, options) {
|
||||
var settings = $.extend({
|
||||
url: url,
|
||||
hintText: "Type in a search term",
|
||||
noResultsText: "No results",
|
||||
searchingText: "Searching...",
|
||||
searchDelay: 300,
|
||||
minChars: 1,
|
||||
tokenLimit: null,
|
||||
jsonContainer: null,
|
||||
method: "GET",
|
||||
contentType: "json",
|
||||
queryParam: "q",
|
||||
onResult: null
|
||||
}, options);
|
||||
|
||||
settings.classes = $.extend({
|
||||
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"
|
||||
}, options.classes);
|
||||
|
||||
return this.each(function () {
|
||||
var list = new $.TokenList(this, settings);
|
||||
});
|
||||
};
|
||||
|
||||
$.TokenList = function (input, settings) {
|
||||
//
|
||||
// Variables
|
||||
//
|
||||
|
||||
// Input box position "enum"
|
||||
var POSITION = {
|
||||
BEFORE: 0,
|
||||
AFTER: 1,
|
||||
END: 2
|
||||
};
|
||||
|
||||
// Keys "enum"
|
||||
var KEY = {
|
||||
BACKSPACE: 8,
|
||||
TAB: 9,
|
||||
RETURN: 13,
|
||||
ESC: 27,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
COMMA: 188
|
||||
};
|
||||
|
||||
// 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
|
||||
var timeout;
|
||||
|
||||
// Create a new text input an attach keyup events
|
||||
var input_box = $("<input type=\"text\">")
|
||||
.css({
|
||||
outline: "none"
|
||||
})
|
||||
.focus(function () {
|
||||
if (settings.tokenLimit == null || settings.tokenLimit != token_count) {
|
||||
show_dropdown_hint();
|
||||
}
|
||||
})
|
||||
.blur(function () {
|
||||
hide_dropdown();
|
||||
})
|
||||
.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(false);}, 5);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.TAB:
|
||||
case KEY.RETURN:
|
||||
case KEY.COMMA:
|
||||
if(selected_dropdown_item) {
|
||||
add_token($(selected_dropdown_item));
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.ESC:
|
||||
hide_dropdown();
|
||||
return true;
|
||||
|
||||
default:
|
||||
if(is_printable_character(event.keyCode)) {
|
||||
// set a timeout just long enough to let this function finish.
|
||||
setTimeout(function(){do_search(false);}, 5);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Keep a reference to the original input box
|
||||
var hidden_input = $(input)
|
||||
.hide()
|
||||
.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_dropdown_item = null;
|
||||
|
||||
// The list to store the token items in
|
||||
var token_list = $("<ul />")
|
||||
.addClass(settings.classes.tokenList)
|
||||
.insertAfter(hidden_input)
|
||||
.click(function (event) {
|
||||
var li = get_element_from_event(event, "li");
|
||||
if(li && li.get(0) != input_token.get(0)) {
|
||||
toggle_select_token(li);
|
||||
return false;
|
||||
} else {
|
||||
input_box.focus();
|
||||
|
||||
if(selected_token) {
|
||||
deselect_token($(selected_token), POSITION.END);
|
||||
}
|
||||
}
|
||||
})
|
||||
.mouseover(function (event) {
|
||||
var li = get_element_from_event(event, "li");
|
||||
if(li && selected_token !== this) {
|
||||
li.addClass(settings.classes.highlightedToken);
|
||||
}
|
||||
})
|
||||
.mouseout(function (event) {
|
||||
var li = get_element_from_event(event, "li");
|
||||
if(li && selected_token !== this) {
|
||||
li.removeClass(settings.classes.highlightedToken);
|
||||
}
|
||||
})
|
||||
.mousedown(function (event) {
|
||||
// Stop user selecting text on tokens
|
||||
var li = get_element_from_event(event, "li");
|
||||
if(li){
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// The list to store the dropdown items in
|
||||
var dropdown = $("<div>")
|
||||
.addClass(settings.classes.dropdown)
|
||||
.insertAfter(token_list)
|
||||
.hide();
|
||||
|
||||
// The token holding the input box
|
||||
var input_token = $("<li />")
|
||||
.addClass(settings.classes.inputToken)
|
||||
.appendTo(token_list)
|
||||
.append(input_box);
|
||||
|
||||
init_list();
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
|
||||
// Pre-populate list if items exist
|
||||
function init_list () {
|
||||
li_data = settings.prePopulate;
|
||||
if(li_data && li_data.length) {
|
||||
for(var i in li_data) {
|
||||
var this_token = $("<li><p>"+li_data[i].name+"</p> </li>")
|
||||
.addClass(settings.classes.token)
|
||||
.insertBefore(input_token);
|
||||
|
||||
$("<span>x</span>")
|
||||
.addClass(settings.classes.tokenDelete)
|
||||
.appendTo(this_token)
|
||||
.click(function () {
|
||||
delete_token($(this).parent());
|
||||
return false;
|
||||
});
|
||||
|
||||
$.data(this_token.get(0), "tokeninput", {"id": li_data[i].id, "name": li_data[i].name});
|
||||
|
||||
// Clear input box and make sure it keeps focus
|
||||
input_box
|
||||
.val("")
|
||||
.focus();
|
||||
|
||||
// Don't show the help dropdown, they've got the idea
|
||||
hide_dropdown();
|
||||
|
||||
// Save this token id
|
||||
var id_string = li_data[i].id + ","
|
||||
hidden_input.val(hidden_input.val() + id_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function is_printable_character(keycode) {
|
||||
if((keycode >= 48 && keycode <= 90) || // 0-1a-z
|
||||
(keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
|
||||
(keycode >= 186 && keycode <= 192) || // ; = , - . / ^
|
||||
(keycode >= 219 && keycode <= 222) // ( \ ) '
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get an element of a particular type from an event (click/mouseover etc)
|
||||
function get_element_from_event (event, element_type) {
|
||||
var target = $(event.target);
|
||||
var element = null;
|
||||
|
||||
if(target.is(element_type)) {
|
||||
element = target;
|
||||
} else if(target.parent(element_type).length) {
|
||||
element = target.parent(element_type+":first");
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
// 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>x</span>")
|
||||
.addClass(settings.classes.tokenDelete)
|
||||
.appendTo(this_token)
|
||||
.click(function () {
|
||||
delete_token($(this).parent());
|
||||
return false;
|
||||
});
|
||||
|
||||
$.data(this_token.get(0), "tokeninput", {"id": id, "name": value});
|
||||
|
||||
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 this_token = insert_token(li_data.id, li_data.name);
|
||||
|
||||
// Clear input box and make sure it keeps focus
|
||||
input_box
|
||||
.val("")
|
||||
.focus();
|
||||
|
||||
// Don't show the help dropdown, they've got the idea
|
||||
hide_dropdown();
|
||||
|
||||
// Save this token id
|
||||
var id_string = li_data.id + ","
|
||||
hidden_input.val(hidden_input.val() + id_string);
|
||||
|
||||
token_count++;
|
||||
|
||||
if(settings.tokenLimit != null && settings.tokenLimit >= token_count) {
|
||||
input_box.hide();
|
||||
hide_dropdown();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
} else if(position == POSITION.AFTER) {
|
||||
input_token.insertAfter(token);
|
||||
} else {
|
||||
input_token.appendTo(token_list);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if(selected_token == token.get(0)) {
|
||||
deselect_token(token, POSITION.END);
|
||||
} else {
|
||||
if(selected_token) {
|
||||
deselect_token($(selected_token), POSITION.END);
|
||||
}
|
||||
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");
|
||||
|
||||
// Delete the token
|
||||
token.remove();
|
||||
selected_token = null;
|
||||
|
||||
// Show the input box and give it focus again
|
||||
input_box.focus();
|
||||
|
||||
// Delete this token's id from hidden input
|
||||
var str = hidden_input.val()
|
||||
var start = str.indexOf(token_data.id+",");
|
||||
var end = str.indexOf(",", start) + 1;
|
||||
|
||||
if(end >= str.length) {
|
||||
hidden_input.val(str.slice(0, start));
|
||||
} else {
|
||||
hidden_input.val(str.slice(0, start) + str.slice(end, str.length));
|
||||
}
|
||||
|
||||
token_count--;
|
||||
|
||||
if (settings.tokenLimit != null) {
|
||||
input_box
|
||||
.show()
|
||||
.val("")
|
||||
.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Hide and clear the results dropdown
|
||||
function hide_dropdown () {
|
||||
dropdown.hide().empty();
|
||||
selected_dropdown_item = null;
|
||||
}
|
||||
|
||||
function show_dropdown_searching () {
|
||||
dropdown
|
||||
.html("<p>"+settings.searchingText+"</p>")
|
||||
.show();
|
||||
}
|
||||
|
||||
function show_dropdown_hint () {
|
||||
dropdown
|
||||
.html("<p>"+settings.hintText+"</p>")
|
||||
.show();
|
||||
}
|
||||
|
||||
// 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.length) {
|
||||
dropdown.empty();
|
||||
var dropdown_ul = $("<ul>")
|
||||
.appendTo(dropdown)
|
||||
.mouseover(function (event) {
|
||||
select_dropdown_item(get_element_from_event(event, "li"));
|
||||
})
|
||||
.click(function (event) {
|
||||
add_token(get_element_from_event(event, "li"));
|
||||
})
|
||||
.mousedown(function (event) {
|
||||
// Stop user selecting text on tokens
|
||||
return false;
|
||||
})
|
||||
.hide();
|
||||
|
||||
for(var i in results) {
|
||||
if (results.hasOwnProperty(i)) {
|
||||
var this_li = $("<li>"+highlight_term(results[i].name, query)+"</li>")
|
||||
.appendTo(dropdown_ul);
|
||||
|
||||
if(i%2) {
|
||||
this_li.addClass(settings.classes.dropdownItem);
|
||||
} else {
|
||||
this_li.addClass(settings.classes.dropdownItem2);
|
||||
}
|
||||
|
||||
if(i == 0) {
|
||||
select_dropdown_item(this_li);
|
||||
}
|
||||
|
||||
$.data(this_li.get(0), "tokeninput", {"id": results[i].id, "name": results[i].name});
|
||||
}
|
||||
}
|
||||
|
||||
dropdown.show();
|
||||
dropdown_ul.slideDown("fast");
|
||||
|
||||
} else {
|
||||
dropdown
|
||||
.html("<p>"+settings.noResultsText+"</p>")
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
// 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(immediate) {
|
||||
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();
|
||||
if (immediate) {
|
||||
run_search(query);
|
||||
} else {
|
||||
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 {
|
||||
var queryStringDelimiter = settings.url.indexOf("?") < 0 ? "?" : "&";
|
||||
var callback = function(results) {
|
||||
if($.isFunction(settings.onResult)) {
|
||||
results = settings.onResult.call(this, results);
|
||||
}
|
||||
cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
|
||||
populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
|
||||
};
|
||||
|
||||
if(settings.method == "POST") {
|
||||
$.post(settings.url + queryStringDelimiter + settings.queryParam + "=" + query, {}, callback, settings.contentType);
|
||||
} else {
|
||||
$.get(settings.url + queryStringDelimiter + settings.queryParam + "=" + query, {}, callback, settings.contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Really basic cache for the results
|
||||
$.TokenList.Cache = function (options) {
|
||||
var settings = $.extend({
|
||||
max_size: 50
|
||||
}, 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++;
|
||||
}
|
||||
|
||||
data[query] = results;
|
||||
};
|
||||
|
||||
this.get = function (query) {
|
||||
return data[query];
|
||||
};
|
||||
};
|
||||
|
||||
})(jQuery);
|
@ -13,6 +13,7 @@ from django.db.models.fields import BLANK_CHOICE_DASH
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.utils import simplejson
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.functional import update_wrapper
|
||||
from django.utils.html import escape
|
||||
@ -747,7 +748,9 @@ class ModelAdmin(BaseModelAdmin):
|
||||
other_qs = other_qs.filter(reduce(operator.or_, or_queries))
|
||||
queryset = queryset & other_qs
|
||||
|
||||
return HttpResponse(''.join([u'%s|%s\n' % (unicode(f), f.pk) for f in queryset]))
|
||||
data = [{"id": f.pk, "name": unicode(f)} for f in queryset]
|
||||
|
||||
return HttpResponse(simplejson.dumps(data))
|
||||
|
||||
def add_view(self, request, form_url='', extra_context=None):
|
||||
"The 'add' admin view for this model."
|
||||
|
@ -7,30 +7,25 @@
|
||||
$(document).ready(function() {
|
||||
// Show lookup input
|
||||
$("#lookup_{{ name }}").show();
|
||||
function reset() {
|
||||
$('#id_{{ name }}').val('');
|
||||
$('#lookup_{{ name }}').val('');
|
||||
};
|
||||
|
||||
$('#lookup_{{ name }}').autocomplete('{{ search_path }}').result(
|
||||
|
||||
$('#lookup_{{ name }}').autocomplete('{{ search_path }}', {
|
||||
dataType: 'json',
|
||||
parse: function(data) {
|
||||
var rows = new Array();
|
||||
for(var i=0; i<data.length; i++){
|
||||
rows[i] = { data:data[i], value:data[i].id, result:data[i].name };
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
formatItem: function(row, i, n) {
|
||||
return row.name;
|
||||
},
|
||||
cacheLength: 0,
|
||||
}).result(
|
||||
function(event, data, formatted) {
|
||||
if (data) {
|
||||
$('#id_{{ name }}').val(data[1]);
|
||||
$('#id_{{ name }}').val(data.id);
|
||||
}
|
||||
}).keyup(function(event){
|
||||
if (event.keyCode == 27) {
|
||||
reset();
|
||||
};
|
||||
});
|
||||
var {{ name }}_value = $('#id_{{ name }}').val();
|
||||
function check() {
|
||||
{{ name }}_check = $('#id_{{ name }}').val();
|
||||
if ({{ name }}_check) {
|
||||
if ({{ name }}_check != {{ name }}_value) {
|
||||
lookup({{ name }}_check);
|
||||
}
|
||||
}
|
||||
}
|
||||
timeout = window.setInterval(check, 300);
|
||||
});
|
||||
</script>
|
@ -1,26 +1,18 @@
|
||||
{% load i18n %}
|
||||
<textarea id="lookup_{{ name }}" style="display:none;">{{ label }}</textarea>
|
||||
|
||||
<a href="{{ related_url }}{{ url }}" class="related-lookup" id="lookup_id_{{ name }}" onclick="return showRelatedObjectLookupPopup(this);">
|
||||
<img src="{{ admin_media_prefix }}img/admin/selector-search.gif" width="16" height="16" alt="{% trans "Lookup" %}" />
|
||||
</a>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
// Show lookup input
|
||||
$("#lookup_{{ name }}").show();
|
||||
|
||||
$('#lookup_{{ name }}').autocomplete('{{ search_path }}', {
|
||||
multiple: true,
|
||||
mustMatch: true
|
||||
}).result(function(event, data, formatted) {
|
||||
if (data) {
|
||||
if ($('#id_{{ name }}').val()) {
|
||||
$('#id_{{ name }}').val($('#id_{{ name }}').val() + "," + data[1]);
|
||||
}
|
||||
else {
|
||||
$('#id_{{ name }}').val(data[1]);
|
||||
}
|
||||
|
||||
}
|
||||
$('#id_{{ name }}').tokenInput("{{ search_path }}", {
|
||||
noResultsText: "No results found.",
|
||||
searchingText: "Searching..."
|
||||
});
|
||||
|
||||
$('#add_id_{{ name }}').hide();
|
||||
$('#lookup_id_{{ name }}').hide();
|
||||
});
|
||||
</script>
|
@ -274,13 +274,11 @@ class ManyToManySearchInput(ManyToManyRawIdWidget):
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': (settings.ADMIN_MEDIA_PREFIX + 'css/jquery.autocomplete.css',)
|
||||
'all': (settings.ADMIN_MEDIA_PREFIX + 'css/jquery.token.input.css',)
|
||||
}
|
||||
js = (
|
||||
settings.ADMIN_MEDIA_PREFIX + 'js/jquery.js',
|
||||
settings.ADMIN_MEDIA_PREFIX + 'js/jquery.bgiframe.min.js',
|
||||
settings.ADMIN_MEDIA_PREFIX + 'js/jquery.ajaxQueue.js',
|
||||
settings.ADMIN_MEDIA_PREFIX + 'js/jquery.autocomplete.js',
|
||||
settings.ADMIN_MEDIA_PREFIX + 'js/jquery.tokeninput.js',
|
||||
)
|
||||
|
||||
def __init__(self, rel, search_fields, attrs=None):
|
||||
|
Loading…
x
Reference in New Issue
Block a user