123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152 |
- /**
- * @fileOverview jquery-autocomplete, the jQuery Autocompleter
- * @author <a href="mailto:dylan@dyve.net">Dylan Verheul</a>
- * @version 2.4.4
- * @requires jQuery 1.6+
- * @license MIT | GPL | Apache 2.0, see LICENSE.txt
- * @see https://github.com/dyve/jquery-autocomplete
- */
- (function($) {
- "use strict";
- /**
- * jQuery autocomplete plugin
- * @param {object|string} options
- * @returns (object} jQuery object
- */
- $.fn.autocomplete = function(options) {
- var url;
- if (arguments.length > 1) {
- url = options;
- options = arguments[1];
- options.url = url;
- } else if (typeof options === 'string') {
- url = options;
- options = { url: url };
- }
- var opts = $.extend({}, $.fn.autocomplete.defaults, options);
- return this.each(function() {
- var $this = $(this);
- $this.data('autocompleter', new $.Autocompleter(
- $this,
- $.meta ? $.extend({}, opts, $this.data()) : opts
- ));
- });
- };
- /**
- * Store default options
- * @type {object}
- */
- $.fn.autocomplete.defaults = {
- inputClass: 'acInput',
- loadingClass: 'acLoading',
- resultsClass: 'acResults',
- selectClass: 'acSelect',
- queryParamName: 'q',
- extraParams: {},
- remoteDataType: false,
- lineSeparator: '\n',
- cellSeparator: '|',
- minChars: 2,
- maxItemsToShow: 10,
- delay: 400,
- useCache: true,
- maxCacheLength: 10,
- matchSubset: true,
- matchCase: false,
- matchInside: true,
- mustMatch: false,
- selectFirst: false,
- selectOnly: false,
- showResult: null,
- preventDefaultReturn: 1,
- preventDefaultTab: 0,
- autoFill: false,
- filterResults: true,
- filter: true,
- sortResults: true,
- sortFunction: null,
- onItemSelect: null,
- onNoMatch: null,
- onFinish: null,
- matchStringConverter: null,
- beforeUseConverter: null,
- autoWidth: 'min-width',
- useDelimiter: false,
- delimiterChar: ',',
- delimiterKeyCode: 188,
- processData: null,
- onError: null,
- enabled: true
- };
- /**
- * Sanitize result
- * @param {Object} result
- * @returns {Object} object with members value (String) and data (Object)
- * @private
- */
- var sanitizeResult = function(result) {
- var value, data;
- var type = typeof result;
- if (type === 'string') {
- value = result;
- data = {};
- } else if ($.isArray(result)) {
- value = result[0];
- data = result.slice(1);
- } else if (type === 'object') {
- value = result.value;
- data = result.data;
- }
- value = String(value);
- if (typeof data !== 'object') {
- data = {};
- }
- return {
- value: value,
- data: data
- };
- };
- /**
- * Sanitize integer
- * @param {mixed} value
- * @param {Object} options
- * @returns {Number} integer
- * @private
- */
- var sanitizeInteger = function(value, stdValue, options) {
- var num = parseInt(value, 10);
- options = options || {};
- if (isNaN(num) || (options.min && num < options.min)) {
- num = stdValue;
- }
- return num;
- };
- /**
- * Create partial url for a name/value pair
- */
- var makeUrlParam = function(name, value) {
- return [name, encodeURIComponent(value)].join('=');
- };
- /**
- * Build an url
- * @param {string} url Base url
- * @param {object} [params] Dictionary of parameters
- */
- var makeUrl = function(url, params) {
- var urlAppend = [];
- $.each(params, function(index, value) {
- urlAppend.push(makeUrlParam(index, value));
- });
- if (urlAppend.length) {
- url += url.indexOf('?') === -1 ? '?' : '&';
- url += urlAppend.join('&');
- }
- return url;
- };
- /**
- * Default sort filter
- * @param {object} a
- * @param {object} b
- * @param {boolean} matchCase
- * @returns {number}
- */
- var sortValueAlpha = function(a, b, matchCase) {
- a = String(a.value);
- b = String(b.value);
- if (!matchCase) {
- a = a.toLowerCase();
- b = b.toLowerCase();
- }
- if (a > b) {
- return 1;
- }
- if (a < b) {
- return -1;
- }
- return 0;
- };
- /**
- * Parse data received in text format
- * @param {string} text Plain text input
- * @param {string} lineSeparator String that separates lines
- * @param {string} cellSeparator String that separates cells
- * @returns {array} Array of autocomplete data objects
- */
- var plainTextParser = function(text, lineSeparator, cellSeparator) {
- var results = [];
- var i, j, data, line, value, lines;
- // Be nice, fix linebreaks before splitting on lineSeparator
- lines = String(text).replace('\r\n', '\n').split(lineSeparator);
- for (i = 0; i < lines.length; i++) {
- line = lines[i].split(cellSeparator);
- data = [];
- for (j = 0; j < line.length; j++) {
- data.push(decodeURIComponent(line[j]));
- }
- value = data.shift();
- results.push({ value: value, data: data });
- }
- return results;
- };
- /**
- * Autocompleter class
- * @param {object} $elem jQuery object with one input tag
- * @param {object} options Settings
- * @constructor
- */
- $.Autocompleter = function($elem, options) {
- /**
- * Assert parameters
- */
- if (!$elem || !($elem instanceof $) || $elem.length !== 1 || $elem.get(0).tagName.toUpperCase() !== 'INPUT') {
- throw new Error('Invalid parameter for jquery.Autocompleter, jQuery object with one element with INPUT tag expected.');
- }
- /**
- * @constant Link to this instance
- * @type object
- * @private
- */
- var self = this;
- /**
- * @property {object} Options for this instance
- * @public
- */
- this.options = options;
- /**
- * @property object Cached data for this instance
- * @private
- */
- this.cacheData_ = {};
- /**
- * @property {number} Number of cached data items
- * @private
- */
- this.cacheLength_ = 0;
- /**
- * @property {string} Class name to mark selected item
- * @private
- */
- this.selectClass_ = 'jquery-autocomplete-selected-item';
- /**
- * @property {number} Handler to activation timeout
- * @private
- */
- this.keyTimeout_ = null;
- /**
- * @property {number} Handler to finish timeout
- * @private
- */
- this.finishTimeout_ = null;
- /**
- * @property {number} Last key pressed in the input field (store for behavior)
- * @private
- */
- this.lastKeyPressed_ = null;
- /**
- * @property {string} Last value processed by the autocompleter
- * @private
- */
- this.lastProcessedValue_ = null;
- /**
- * @property {string} Last value selected by the user
- * @private
- */
- this.lastSelectedValue_ = null;
- /**
- * @property {boolean} Is this autocompleter active (showing results)?
- * @see showResults
- * @private
- */
- this.active_ = false;
- /**
- * @property {boolean} Is this autocompleter allowed to finish on blur?
- * @private
- */
- this.finishOnBlur_ = true;
- /**
- * Sanitize options
- */
- this.options.minChars = sanitizeInteger(this.options.minChars, $.fn.autocomplete.defaults.minChars, { min: 0 });
- this.options.maxItemsToShow = sanitizeInteger(this.options.maxItemsToShow, $.fn.autocomplete.defaults.maxItemsToShow, { min: 0 });
- this.options.maxCacheLength = sanitizeInteger(this.options.maxCacheLength, $.fn.autocomplete.defaults.maxCacheLength, { min: 1 });
- this.options.delay = sanitizeInteger(this.options.delay, $.fn.autocomplete.defaults.delay, { min: 0 });
- if (this.options.preventDefaultReturn != 2) {
- this.options.preventDefaultReturn = this.options.preventDefaultReturn ? 1 : 0;
- }
- if (this.options.preventDefaultTab != 2) {
- this.options.preventDefaultTab = this.options.preventDefaultTab ? 1 : 0;
- }
- /**
- * Init DOM elements repository
- */
- this.dom = {};
- /**
- * Store the input element we're attached to in the repository
- */
- this.dom.$elem = $elem;
- /**
- * Switch off the native autocomplete and add the input class
- */
- this.dom.$elem.attr('autocomplete', 'off').addClass(this.options.inputClass);
- /**
- * Create DOM element to hold results, and force absolute position
- */
- this.dom.$results = $('<div></div>').hide().addClass(this.options.resultsClass).css({
- position: 'absolute'
- });
- $('body').append(this.dom.$results);
- /**
- * Attach keyboard monitoring to $elem
- */
- $elem.keydown(function(e) {
- self.lastKeyPressed_ = e.keyCode;
- switch(self.lastKeyPressed_) {
- case self.options.delimiterKeyCode: // comma = 188
- if (self.options.useDelimiter && self.active_) {
- self.selectCurrent();
- }
- break;
- // ignore navigational & special keys
- case 35: // end
- case 36: // home
- case 16: // shift
- case 17: // ctrl
- case 18: // alt
- case 37: // left
- case 39: // right
- break;
- case 38: // up
- e.preventDefault();
- if (self.active_) {
- self.focusPrev();
- } else {
- self.activate();
- }
- return false;
- case 40: // down
- e.preventDefault();
- if (self.active_) {
- self.focusNext();
- } else {
- self.activate();
- }
- return false;
- case 9: // tab
- if (self.active_) {
- self.selectCurrent();
- if (self.options.preventDefaultTab) {
- e.preventDefault();
- return false;
- }
- }
- if (self.options.preventDefaultTab === 2) {
- e.preventDefault();
- return false;
- }
- break;
- case 13: // return
- if (self.active_) {
- self.selectCurrent();
- if (self.options.preventDefaultReturn) {
- e.preventDefault();
- return false;
- }
- }
- if (self.options.preventDefaultReturn === 2) {
- e.preventDefault();
- return false;
- }
- break;
- case 27: // escape
- if (self.active_) {
- e.preventDefault();
- self.deactivate(true);
- return false;
- }
- break;
- default:
- self.activate();
- }
- });
- /**
- * Attach paste event listener because paste may occur much later then keydown or even without a keydown at all
- */
- $elem.on('paste', function() {
- self.activate();
- });
- /**
- * Finish on blur event
- * Use a timeout because instant blur gives race conditions
- */
- var onBlurFunction = function() {
- self.deactivate(true);
- }
- $elem.blur(function() {
- if (self.finishOnBlur_) {
- self.finishTimeout_ = setTimeout(onBlurFunction, 200);
- }
- });
- /**
- * Catch a race condition on form submit
- */
- $elem.parents('form').on('submit', onBlurFunction);
- };
- /**
- * Position output DOM elements
- * @private
- */
- $.Autocompleter.prototype.position = function() {
- var offset = this.dom.$elem.offset();
- var height = this.dom.$results.outerHeight();
- var totalHeight = $(window).outerHeight();
- var inputBottom = offset.top + this.dom.$elem.outerHeight();
- var bottomIfDown = inputBottom + height;
- // Set autocomplete results at the bottom of input
- var position = {top: inputBottom, left: offset.left};
- if (bottomIfDown > totalHeight) {
- // Try to set autocomplete results at the top of input
- var topIfUp = offset.top - height;
- if (topIfUp >= 0) {
- position.top = topIfUp;
- }
- }
- this.dom.$results.css(position);
- };
- /**
- * Read from cache
- * @private
- */
- $.Autocompleter.prototype.cacheRead = function(filter) {
- var filterLength, searchLength, search, maxPos, pos;
- if (this.options.useCache) {
- filter = String(filter);
- filterLength = filter.length;
- if (this.options.matchSubset) {
- searchLength = 1;
- } else {
- searchLength = filterLength;
- }
- while (searchLength <= filterLength) {
- if (this.options.matchInside) {
- maxPos = filterLength - searchLength;
- } else {
- maxPos = 0;
- }
- pos = 0;
- while (pos <= maxPos) {
- search = filter.substr(0, searchLength);
- if (this.cacheData_[search] !== undefined) {
- return this.cacheData_[search];
- }
- pos++;
- }
- searchLength++;
- }
- }
- return false;
- };
- /**
- * Write to cache
- * @private
- */
- $.Autocompleter.prototype.cacheWrite = function(filter, data) {
- if (this.options.useCache) {
- if (this.cacheLength_ >= this.options.maxCacheLength) {
- this.cacheFlush();
- }
- filter = String(filter);
- if (this.cacheData_[filter] !== undefined) {
- this.cacheLength_++;
- }
- this.cacheData_[filter] = data;
- return this.cacheData_[filter];
- }
- return false;
- };
- /**
- * Flush cache
- * @public
- */
- $.Autocompleter.prototype.cacheFlush = function() {
- this.cacheData_ = {};
- this.cacheLength_ = 0;
- };
- /**
- * Call hook
- * Note that all called hooks are passed the autocompleter object
- * @param {string} hook
- * @param data
- * @returns Result of called hook, false if hook is undefined
- */
- $.Autocompleter.prototype.callHook = function(hook, data) {
- var f = this.options[hook];
- if (f && $.isFunction(f)) {
- return f(data, this);
- }
- return false;
- };
- /**
- * Set timeout to activate autocompleter
- */
- $.Autocompleter.prototype.activate = function() {
- if (!this.options.enabled) return;
- var self = this;
- if (this.keyTimeout_) {
- clearTimeout(this.keyTimeout_);
- }
- this.keyTimeout_ = setTimeout(function() {
- self.activateNow();
- }, this.options.delay);
- };
- /**
- * Activate autocompleter immediately
- */
- $.Autocompleter.prototype.activateNow = function() {
- var value = this.beforeUseConverter(this.dom.$elem.val());
- if (value !== this.lastProcessedValue_ && value !== this.lastSelectedValue_) {
- this.fetchData(value);
- }
- };
- /**
- * Get autocomplete data for a given value
- * @param {string} value Value to base autocompletion on
- * @private
- */
- $.Autocompleter.prototype.fetchData = function(value) {
- var self = this;
- var processResults = function(results, filter) {
- if (self.options.processData) {
- results = self.options.processData(results);
- }
- self.showResults(self.filterResults(results, filter), filter);
- };
- this.lastProcessedValue_ = value;
- if (value.length < this.options.minChars) {
- processResults([], value);
- } else if (this.options.data) {
- processResults(this.options.data, value);
- } else {
- this.fetchRemoteData(value, function(remoteData) {
- processResults(remoteData, value);
- });
- }
- };
- /**
- * Get remote autocomplete data for a given value
- * @param {string} filter The filter to base remote data on
- * @param {function} callback The function to call after data retrieval
- * @private
- */
- $.Autocompleter.prototype.fetchRemoteData = function(filter, callback) {
- var data = this.cacheRead(filter);
- if (data) {
- callback(data);
- } else {
- var self = this;
- var dataType = self.options.remoteDataType === 'json' ? 'json' : 'text';
- var ajaxCallback = function(data) {
- var parsed = false;
- if (data !== false) {
- parsed = self.parseRemoteData(data);
- self.cacheWrite(filter, parsed);
- }
- self.dom.$elem.removeClass(self.options.loadingClass);
- callback(parsed);
- };
- this.dom.$elem.addClass(this.options.loadingClass);
- $.ajax({
- url: this.makeUrl(filter),
- success: ajaxCallback,
- error: function(jqXHR, textStatus, errorThrown) {
- if($.isFunction(self.options.onError)) {
- self.options.onError(jqXHR, textStatus, errorThrown);
- } else {
- ajaxCallback(false);
- }
- },
- dataType: dataType
- });
- }
- };
- /**
- * Create or update an extra parameter for the remote request
- * @param {string} name Parameter name
- * @param {string} value Parameter value
- * @public
- */
- $.Autocompleter.prototype.setExtraParam = function(name, value) {
- var index = $.trim(String(name));
- if (index) {
- if (!this.options.extraParams) {
- this.options.extraParams = {};
- }
- if (this.options.extraParams[index] !== value) {
- this.options.extraParams[index] = value;
- this.cacheFlush();
- }
- }
- return this;
- };
- /**
- * Build the url for a remote request
- * If options.queryParamName === false, append query to url instead of using a GET parameter
- * @param {string} param The value parameter to pass to the backend
- * @returns {string} The finished url with parameters
- */
- $.Autocompleter.prototype.makeUrl = function(param) {
- var self = this;
- var url = this.options.url;
- var params = $.extend({}, this.options.extraParams);
- if (this.options.queryParamName === false) {
- url += encodeURIComponent(param);
- } else {
- params[this.options.queryParamName] = param;
- }
- return makeUrl(url, params);
- };
- /**
- * Parse data received from server
- * @param remoteData Data received from remote server
- * @returns {array} Parsed data
- */
- $.Autocompleter.prototype.parseRemoteData = function(remoteData) {
- var remoteDataType;
- var data = remoteData;
- if (this.options.remoteDataType === 'json') {
- remoteDataType = typeof(remoteData);
- switch (remoteDataType) {
- case 'object':
- data = remoteData;
- break;
- case 'string':
- data = $.parseJSON(remoteData);
- break;
- default:
- throw new Error("Unexpected remote data type: " + remoteDataType);
- }
- return data;
- }
- return plainTextParser(data, this.options.lineSeparator, this.options.cellSeparator);
- };
- /**
- * Default filter for results
- * @param {Object} result
- * @param {String} filter
- * @returns {boolean} Include this result
- * @private
- */
- $.Autocompleter.prototype.defaultFilter = function(result, filter) {
- if (!result.value) {
- return false;
- }
- if (this.options.filterResults) {
- var pattern = this.matchStringConverter(filter);
- var testValue = this.matchStringConverter(result.value);
- if (!this.options.matchCase) {
- pattern = pattern.toLowerCase();
- testValue = testValue.toLowerCase();
- }
- var patternIndex = testValue.indexOf(pattern);
- if (this.options.matchInside) {
- return patternIndex > -1;
- } else {
- return patternIndex === 0;
- }
- }
- return true;
- };
- /**
- * Filter result
- * @param {Object} result
- * @param {String} filter
- * @returns {boolean} Include this result
- * @private
- */
- $.Autocompleter.prototype.filterResult = function(result, filter) {
- // No filter
- if (this.options.filter === false) {
- return true;
- }
- // Custom filter
- if ($.isFunction(this.options.filter)) {
- return this.options.filter(result, filter);
- }
- // Default filter
- return this.defaultFilter(result, filter);
- };
- /**
- * Filter results
- * @param results
- * @param filter
- */
- $.Autocompleter.prototype.filterResults = function(results, filter) {
- var filtered = [];
- var i, result;
- for (i = 0; i < results.length; i++) {
- result = sanitizeResult(results[i]);
- if (this.filterResult(result, filter)) {
- filtered.push(result);
- }
- }
- if (this.options.sortResults) {
- filtered = this.sortResults(filtered, filter);
- }
- if (this.options.maxItemsToShow > 0 && this.options.maxItemsToShow < filtered.length) {
- filtered.length = this.options.maxItemsToShow;
- }
- return filtered;
- };
- /**
- * Sort results
- * @param results
- * @param filter
- */
- $.Autocompleter.prototype.sortResults = function(results, filter) {
- var self = this;
- var sortFunction = this.options.sortFunction;
- if (!$.isFunction(sortFunction)) {
- sortFunction = function(a, b, f) {
- return sortValueAlpha(a, b, self.options.matchCase);
- };
- }
- results.sort(function(a, b) {
- return sortFunction(a, b, filter, self.options);
- });
- return results;
- };
- /**
- * Convert string before matching
- * @param s
- * @param a
- * @param b
- */
- $.Autocompleter.prototype.matchStringConverter = function(s, a, b) {
- var converter = this.options.matchStringConverter;
- if ($.isFunction(converter)) {
- s = converter(s, a, b);
- }
- return s;
- };
- /**
- * Convert string before use
- * @param {String} s
- */
- $.Autocompleter.prototype.beforeUseConverter = function(s) {
- s = this.getValue(s);
- var converter = this.options.beforeUseConverter;
- if ($.isFunction(converter)) {
- s = converter(s);
- }
- return s;
- };
- /**
- * Enable finish on blur event
- */
- $.Autocompleter.prototype.enableFinishOnBlur = function() {
- this.finishOnBlur_ = true;
- };
- /**
- * Disable finish on blur event
- */
- $.Autocompleter.prototype.disableFinishOnBlur = function() {
- this.finishOnBlur_ = false;
- };
- /**
- * Create a results item (LI element) from a result
- * @param result
- */
- $.Autocompleter.prototype.createItemFromResult = function(result) {
- var self = this;
- var $li = $('<li/>');
- $li.html(this.showResult(result.value, result.data));
- $li.data({value: result.value, data: result.data})
- .click(function() {
- self.selectItem($li);
- })
- .mousedown(self.disableFinishOnBlur)
- .mouseup(self.enableFinishOnBlur)
- ;
- return $li;
- };
- /**
- * Get all items from the results list
- * @param result
- */
- $.Autocompleter.prototype.getItems = function() {
- return $('>ul>li', this.dom.$results);
- };
- /**
- * Show all results
- * @param results
- * @param filter
- */
- $.Autocompleter.prototype.showResults = function(results, filter) {
- var numResults = results.length;
- var self = this;
- var $ul = $('<ul></ul>');
- var i, result, $li, autoWidth, first = false, $first = false;
- if (numResults) {
- for (i = 0; i < numResults; i++) {
- result = results[i];
- $li = this.createItemFromResult(result);
- $ul.append($li);
- if (first === false) {
- first = String(result.value);
- $first = $li;
- $li.addClass(this.options.firstItemClass);
- }
- if (i === numResults - 1) {
- $li.addClass(this.options.lastItemClass);
- }
- }
- this.dom.$results.html($ul).show();
- // Always recalculate position since window size or
- // input element location may have changed.
- this.position();
- if (this.options.autoWidth) {
- autoWidth = this.dom.$elem.outerWidth() - this.dom.$results.outerWidth() + this.dom.$results.width();
- this.dom.$results.css(this.options.autoWidth, autoWidth);
- }
- this.getItems().hover(
- function() { self.focusItem(this); },
- function() { /* void */ }
- );
- if (this.autoFill(first, filter) || this.options.selectFirst || (this.options.selectOnly && numResults === 1)) {
- this.focusItem($first);
- }
- this.active_ = true;
- } else {
- this.hideResults();
- this.active_ = false;
- }
- };
- $.Autocompleter.prototype.showResult = function(value, data) {
- if ($.isFunction(this.options.showResult)) {
- return this.options.showResult(value, data);
- } else {
- return $('<p></p>').text(value).html();
- }
- };
- $.Autocompleter.prototype.autoFill = function(value, filter) {
- var lcValue, lcFilter, valueLength, filterLength;
- if (this.options.autoFill && this.lastKeyPressed_ !== 8) {
- lcValue = String(value).toLowerCase();
- lcFilter = String(filter).toLowerCase();
- valueLength = value.length;
- filterLength = filter.length;
- if (lcValue.substr(0, filterLength) === lcFilter) {
- var d = this.getDelimiterOffsets();
- var pad = d.start ? ' ' : ''; // if there is a preceding delimiter
- this.setValue( pad + value );
- var start = filterLength + d.start + pad.length;
- var end = valueLength + d.start + pad.length;
- this.selectRange(start, end);
- return true;
- }
- }
- return false;
- };
- $.Autocompleter.prototype.focusNext = function() {
- this.focusMove(+1);
- };
- $.Autocompleter.prototype.focusPrev = function() {
- this.focusMove(-1);
- };
- $.Autocompleter.prototype.focusMove = function(modifier) {
- var $items = this.getItems();
- modifier = sanitizeInteger(modifier, 0);
- if (modifier) {
- for (var i = 0; i < $items.length; i++) {
- if ($($items[i]).hasClass(this.selectClass_)) {
- this.focusItem(i + modifier);
- return;
- }
- }
- }
- this.focusItem(0);
- };
- $.Autocompleter.prototype.focusItem = function(item) {
- var $item, $items = this.getItems();
- if ($items.length) {
- $items.removeClass(this.selectClass_).removeClass(this.options.selectClass);
- if (typeof item === 'number') {
- if (item < 0) {
- item = 0;
- } else if (item >= $items.length) {
- item = $items.length - 1;
- }
- $item = $($items[item]);
- } else {
- $item = $(item);
- }
- if ($item) {
- $item.addClass(this.selectClass_).addClass(this.options.selectClass);
- }
- }
- };
- $.Autocompleter.prototype.selectCurrent = function() {
- var $item = $('li.' + this.selectClass_, this.dom.$results);
- if ($item.length === 1) {
- this.selectItem($item);
- } else {
- this.deactivate(false);
- }
- };
- $.Autocompleter.prototype.selectItem = function($li) {
- var value = $li.data('value');
- var data = $li.data('data');
- var displayValue = this.displayValue(value, data);
- var processedDisplayValue = this.beforeUseConverter(displayValue);
- this.lastProcessedValue_ = processedDisplayValue;
- this.lastSelectedValue_ = processedDisplayValue;
- var d = this.getDelimiterOffsets();
- var delimiter = this.options.delimiterChar;
- var elem = this.dom.$elem;
- var extraCaretPos = 0;
- if ( this.options.useDelimiter ) {
- // if there is a preceding delimiter, add a space after the delimiter
- if ( elem.val().substring(d.start-1, d.start) == delimiter && delimiter != ' ' ) {
- displayValue = ' ' + displayValue;
- }
- // if there is not already a delimiter trailing this value, add it
- if ( elem.val().substring(d.end, d.end+1) != delimiter && this.lastKeyPressed_ != this.options.delimiterKeyCode ) {
- displayValue = displayValue + delimiter;
- } else {
- // move the cursor after the existing trailing delimiter
- extraCaretPos = 1;
- }
- }
- this.setValue(displayValue);
- this.setCaret(d.start + displayValue.length + extraCaretPos);
- this.callHook('onItemSelect', { value: value, data: data });
- this.deactivate(true);
- elem.focus();
- };
- $.Autocompleter.prototype.displayValue = function(value, data) {
- if ($.isFunction(this.options.displayValue)) {
- return this.options.displayValue(value, data);
- }
- return value;
- };
- $.Autocompleter.prototype.hideResults = function() {
- this.dom.$results.hide();
- };
- $.Autocompleter.prototype.deactivate = function(finish) {
- if (this.finishTimeout_) {
- clearTimeout(this.finishTimeout_);
- }
- if (this.keyTimeout_) {
- clearTimeout(this.keyTimeout_);
- }
- if (finish) {
- if (this.lastProcessedValue_ !== this.lastSelectedValue_) {
- if (this.options.mustMatch) {
- this.setValue('');
- }
- this.callHook('onNoMatch');
- }
- if (this.active_) {
- this.callHook('onFinish');
- }
- this.lastKeyPressed_ = null;
- this.lastProcessedValue_ = null;
- this.lastSelectedValue_ = null;
- this.active_ = false;
- }
- this.hideResults();
- };
- $.Autocompleter.prototype.selectRange = function(start, end) {
- var input = this.dom.$elem.get(0);
- if (input.setSelectionRange) {
- input.focus();
- input.setSelectionRange(start, end);
- } else if (input.createTextRange) {
- var range = input.createTextRange();
- range.collapse(true);
- range.moveEnd('character', end);
- range.moveStart('character', start);
- range.select();
- }
- };
- /**
- * Move caret to position
- * @param {Number} pos
- */
- $.Autocompleter.prototype.setCaret = function(pos) {
- this.selectRange(pos, pos);
- };
- /**
- * Get caret position
- */
- $.Autocompleter.prototype.getCaret = function() {
- var $elem = this.dom.$elem;
- var elem = $elem[0];
- var val, selection, range, start, end, stored_range;
- if (elem.createTextRange) { // IE
- selection = document.selection;
- if (elem.tagName.toLowerCase() != 'textarea') {
- val = $elem.val();
- range = selection.createRange().duplicate();
- range.moveEnd('character', val.length);
- if (range.text === '') {
- start = val.length;
- } else {
- start = val.lastIndexOf(range.text);
- }
- range = selection.createRange().duplicate();
- range.moveStart('character', -val.length);
- end = range.text.length;
- } else {
- range = selection.createRange();
- stored_range = range.duplicate();
- stored_range.moveToElementText(elem);
- stored_range.setEndPoint('EndToEnd', range);
- start = stored_range.text.length - range.text.length;
- end = start + range.text.length;
- }
- } else {
- start = $elem[0].selectionStart;
- end = $elem[0].selectionEnd;
- }
- return {
- start: start,
- end: end
- };
- };
- /**
- * Set the value that is currently being autocompleted
- * @param {String} value
- */
- $.Autocompleter.prototype.setValue = function(value) {
- if ( this.options.useDelimiter ) {
- // set the substring between the current delimiters
- var val = this.dom.$elem.val();
- var d = this.getDelimiterOffsets();
- var preVal = val.substring(0, d.start);
- var postVal = val.substring(d.end);
- value = preVal + value + postVal;
- }
- this.dom.$elem.val(value);
- };
- /**
- * Get the value currently being autocompleted
- * @param {String} value
- */
- $.Autocompleter.prototype.getValue = function(value) {
- if ( this.options.useDelimiter ) {
- var d = this.getDelimiterOffsets();
- return value.substring(d.start, d.end).trim();
- } else {
- return value;
- }
- };
- /**
- * Get the offsets of the value currently being autocompleted
- */
- $.Autocompleter.prototype.getDelimiterOffsets = function() {
- var val = this.dom.$elem.val();
- if ( this.options.useDelimiter ) {
- var preCaretVal = val.substring(0, this.getCaret().start);
- var start = preCaretVal.lastIndexOf(this.options.delimiterChar) + 1;
- var postCaretVal = val.substring(this.getCaret().start);
- var end = postCaretVal.indexOf(this.options.delimiterChar);
- if ( end == -1 ) end = val.length;
- end += this.getCaret().start;
- } else {
- start = 0;
- end = val.length;
- }
- return {
- start: start,
- end: end
- };
- };
- })((typeof window.jQuery == 'undefined' && typeof window.django != 'undefined')? django.jQuery : jQuery);
|