X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/362ea2e7f8ac4254398791139cc060090199f400..fb10f5b89c782ff4e25d0100e09a8c1ba4002de7:/wolnelektury/static/js/jquery.autocomplete.js diff --git a/wolnelektury/static/js/jquery.autocomplete.js b/wolnelektury/static/js/jquery.autocomplete.js index 5ad9178f8..6f46e1b9c 100644 --- a/wolnelektury/static/js/jquery.autocomplete.js +++ b/wolnelektury/static/js/jquery.autocomplete.js @@ -1,18 +1,22 @@ /* - * 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 15 2009-08-22 10:30:27Z joern.zaefferer $ + */ + +/* + * Modified by Radek Czajka, Fundacja Nowoczesna Polska, 2010-05-10: + * escape regex for word start checking in matchSubset */ ;(function($) { - + $.fn.extend({ autocomplete: function(urlOrData, options) { var isUrl = typeof urlOrData == "string"; @@ -22,13 +26,13 @@ $.fn.extend({ delay: isUrl ? $.Autocompleter.defaults.delay : 10, max: options && !options.scroll ? 10 : 150 }, options); - + // if highlight is set to false, replace it with a do-nothing function options.highlight = options.highlight || function(value) { return value; }; - + // if the formatMatch option is not specified, then use formatItem for backwards compatibility options.formatMatch = options.formatMatch || options.formatItem; - + return this.each(function() { new $.Autocompleter(this, options); }); @@ -77,9 +81,9 @@ $.Autocompleter = function(input, options) { mouseDownOnSelect: false }; var select = $.Autocompleter.Select(options, input, selectCurrent, config); - + var blockSubmit; - + // prevent form submit in opera when selecting with return key $.browser.opera && $(input.form).bind("submit.autocomplete", function() { if (blockSubmit) { @@ -87,13 +91,16 @@ $.Autocompleter = function(input, options) { return false; } }); - + // 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) { - + case KEY.UP: event.preventDefault(); if ( select.visible() ) { @@ -102,7 +109,7 @@ $.Autocompleter = function(input, options) { onChange(0, true); } break; - + case KEY.DOWN: event.preventDefault(); if ( select.visible() ) { @@ -111,7 +118,7 @@ $.Autocompleter = function(input, options) { onChange(0, true); } break; - + case KEY.PAGEUP: event.preventDefault(); if ( select.visible() ) { @@ -120,7 +127,7 @@ $.Autocompleter = function(input, options) { onChange(0, true); } break; - + case KEY.PAGEDOWN: event.preventDefault(); if ( select.visible() ) { @@ -129,7 +136,7 @@ $.Autocompleter = function(input, options) { onChange(0, true); } break; - + // matches also semicolon case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: case KEY.TAB: @@ -141,11 +148,11 @@ $.Autocompleter = function(input, options) { return false; } break; - + case KEY.ESC: select.hide(); break; - + default: clearTimeout(timeout); timeout = setTimeout(onChange, options.delay); @@ -196,43 +203,57 @@ $.Autocompleter = function(input, options) { $input.unbind(); $(input.form).unbind(".autocomplete"); }); - - + + function selectCurrent() { var selected = select.selected(); if( !selected ) return false; - + var v = selected.result; previousValue = v; - + 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; } - + $input.val(v); hideResultsNow(); $input.trigger("result", [selected.data, selected.value]); return true; } - + function onChange(crap, skipPrevCheck) { if( lastKeyPressCode == KEY.DEL ) { select.hide(); return; } - + var currentValue = $input.val(); - + if ( !skipPrevCheck && currentValue == previousValue ) return; - + previousValue = currentValue; - + currentValue = lastWord(currentValue); if ( currentValue.length >= options.minChars) { $input.addClass(options.loadingClass); @@ -244,27 +265,32 @@ $.Autocompleter = function(input, options) { select.hide(); } }; - + 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]; } - + // fills in the input box w/the first match (assumed to be the best match) // q: the term entered // sValue: the first matching result @@ -275,7 +301,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 +325,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) { @@ -330,14 +355,14 @@ $.Autocompleter = function(input, options) { success(term, data); // if an AJAX url has been supplied, try loading the data now } else if( (typeof options.url == "string") && (options.url.length > 0) ){ - + var extraParams = { timestamp: +new Date() }; $.each(options.extraParams, function(key, param) { extraParams[key] = typeof param == "function" ? param() : param; }); - + $.ajax({ // try to leverage ajaxQueue plugin to abort previous requests mode: "abort", @@ -361,7 +386,7 @@ $.Autocompleter = function(input, options) { failure(term); } }; - + function parse(data) { var parsed = []; var rows = data.split("\n"); @@ -405,8 +430,22 @@ $.Autocompleter.defaults = { width: 0, multiple: false, multipleSeparator: ", ", + regex_escape: function(term) { + term = term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1"); + /* no polish diacritics; should be more locale-aware */ + term = term.replace(/a/g, '[aÄ ]') + .replace(/c/g, '[cÄ]') + .replace(/e/g, '[eÄ]') + .replace(/l/g, '[lÅ]') + .replace(/n/g, '[nÅ]') + .replace(/o/g, '[oó]') + .replace(/s/g, '[sÅ]') + .replace(/z/g, '[zźż]'); + return term; + }, highlight: function(value, term) { - return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + term = $.Autocompleter.defaults.regex_escape(term); + return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); }, scroll: true, scrollHeight: 180 @@ -416,25 +455,29 @@ $.Autocompleter.Cache = function(options) { var data = {}; var length = 0; - + function matchSubset(s, sub) { - if (!options.matchCase) + if (!options.matchCase) s = s.toLowerCase(); var i = s.indexOf(sub); + if (options.matchContains == "word"){ + query = $.Autocompleter.defaults.regex_escape(sub.toLowerCase()); + i = s.toLowerCase().search("\\b" + query); + } if (i == -1) return false; return i == 0 || options.matchContains; }; - + function add(q, value) { if (length > options.cacheLength){ flush(); } - if (!data[q]){ + if (!data[q]){ length++; } data[q] = value; } - + function populate(){ if( !options.data ) return false; // track the matches @@ -443,23 +486,23 @@ $.Autocompleter.Cache = function(options) { // no url was specified, we need to adjust the cache length to make sure it fits the local data store if( !options.url ) options.cacheLength = 1; - + // track all options for minChars = 0 stMatchSets[""] = []; - + // loop through the array and create a lookup structure for ( var i = 0, ol = options.data.length; i < ol; i++ ) { var rawValue = options.data[i]; // if rawValue is a string, make an array otherwise just reference the array rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; - + var value = options.formatMatch(rawValue, i+1, options.data.length); if ( value === false ) continue; - + var firstChar = value.charAt(0).toLowerCase(); // if no lookup array for this character exists, look it up now - if( !stMatchSets[firstChar] ) + if( !stMatchSets[firstChar] ) stMatchSets[firstChar] = []; // if the match is a string @@ -468,7 +511,7 @@ $.Autocompleter.Cache = function(options) { data: rawValue, result: options.formatResult && options.formatResult(rawValue) || value }; - + // push the current match into the set list stMatchSets[firstChar].push(row); @@ -486,15 +529,15 @@ $.Autocompleter.Cache = function(options) { add(i, value); }); } - + // populate any existing data setTimeout(populate, 25); - + function flush(){ data = {}; length = 0; } - + return { flush: flush, add: add, @@ -502,7 +545,7 @@ $.Autocompleter.Cache = function(options) { load: function(q) { if (!options.cacheLength || !length) return null; - /* + /* * if dealing w/local data and matchContains than we must make sure * to loop through all the data collections looking for matches */ @@ -522,9 +565,9 @@ $.Autocompleter.Cache = function(options) { } }); } - } + } return csub; - } else + } else // if the exact item exists, use it if (data[q]){ return data[q]; @@ -552,7 +595,7 @@ $.Autocompleter.Select = function (options, input, select, config) { var CLASSES = { ACTIVE: "ac_over" }; - + var listItems, active = -1, data, @@ -560,7 +603,7 @@ $.Autocompleter.Select = function (options, input, select, config) { needsInit = true, element, list; - + // Create results function init() { if (!needsInit) @@ -570,11 +613,11 @@ $.Autocompleter.Select = function (options, input, select, config) { .addClass(options.resultsClass) .css("position", "absolute") .appendTo(document.body); - + list = $("