123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958 |
- /**
- * @fileOverview Better Autocomplete is a flexible jQuery plugin which offers
- * rich text autocompletion, both from local and remote sources.
- *
- * @author Didrik Nordström, http://betamos.se/
- *
- * @version v1.0-dev
- *
- * @requires
- * <ul><li>
- * jQuery 1.4+
- * </li><li>
- * IE7+ or any decent webkit/gecko-based web browser
- * </li></ul>
- *
- * @preserve Better Autocomplete v1.0-dev
- * https://github.com/betamos/Better-Autocomplete
- *
- * Copyright 2011, Didrik Nordström, http://betamos.se/
- * Dual licensed under the MIT or GPL Version 2 licenses.
- *
- * Requires jQuery 1.4+
- * http://jquery.com/
- */
- /**
- * Create or alter an autocomplete object instance that belongs to
- * the elements in the selection. Make sure there are only text field elements
- * in the selection.
- *
- * @constructor
- *
- * @name jQuery.betterAutocomplete
- *
- * @param {String} method
- * Should be one of the following:
- * <ul><li>
- * init: Initiate Better Autocomplete instances on the text input elements
- * in the current jQuery selection. They are enabled by default. The other
- * parameters are then required.
- * </li><li>
- * enable: In this jQuery selection, reenable the Better Autocomplete
- * instances.
- * </li><li>
- * disable: In this jQuery selection, disable the Better Autocomplete
- * instances.
- * </li><li>
- * destroy: In this jQuery selection, destroy the Better Autocomplete
- * instances. It will not be possible to reenable them after this.
- * </li></ul>
- *
- * @param {String|Object} [resource]
- * If String, it will become the path for a remote resource. If not, it will
- * be treated like a local resource. The path should provide JSON objects
- * upon HTTP requests.
- *
- * @param {Object} [options]
- * An object with configurable options:
- * <ul><li>
- * charLimit: (default=3 for remote or 1 for local resource) The minimum
- * number of chars to do an AJAX call. A typical use case for this limit is
- * to reduce server load.
- * </li><li>
- * delay: (default=350) The time in ms between last keypress and AJAX call.
- * Typically used to prevent looking up irrelevant strings while the user
- * is still typing. Only relevant for remote resources.
- * </li><li>
- * caseSensitive: (default=false) If the search should be case sensitive.
- * If false, query strings will be converted to lowercase.
- * </li><li>
- * cacheLimit: (default=256 for remote or 0 for local resource) The maximum
- * number of result objects to store in the cache. This option reduces
- * server load if the user deletes characters to check back on previous
- * results. To disable caching of previous results, set this option to 0.
- * </li><li>
- * remoteTimeout: (default=10000) The timeout for remote (AJAX) calls.
- * </li><li>
- * crossOrigin: (default=false) Set to true if cross origin requests will
- * be performed, i.e. that the remote URL has a different domain. This will
- * force Internet Explorer to use "jsonp" instead of "json" as datatype.
- * </li><li>
- * selectKeys: (default=[9, 13]) The key codes for keys which will select
- * the current highlighted element. The defaults are tab, enter.
- * </li><li>
- * autoHighlight: (default=true) Automatically highlight the first result.
- * </li></ul>
- *
- * @param {Object} [callbacks]
- * An object containing optional callback functions on certain events. See
- * {@link callbacks} for details. These callbacks should be used when
- * customization of the default behavior of Better Autocomplete is required.
- *
- * @returns {Object}
- * The jQuery object with the same element selection, for chaining.
- */
- (function($) {
- $.fn.betterAutocomplete = function(method) {
- /*
- * Each method expects the "this" object to be a valid DOM text input node.
- * The methods "enable", "disable" and "destroy" expects an instance of a
- * BetterAutocomplete object as their first argument.
- */
- var methods = {
- init: function(resource, options, callbacks) {
- var $input = $(this),
- bac = new BetterAutocomplete($input, resource, options, callbacks);
- $input.data('better-autocomplete', bac);
- bac.enable();
- },
- enable: function(bac) {
- bac.enable();
- },
- disable: function(bac) {
- bac.disable();
- },
- destroy: function(bac) {
- bac.destroy();
- }
- }, args = Array.prototype.slice.call(arguments, 1);
- // Method calling logic
- this.each(function() {
- switch (method) {
- case 'init':
- methods[method].apply(this, args);
- break;
- case 'enable':
- case 'disable':
- case 'destroy':
- var bac = $(this).data('better-autocomplete');
- if (bac instanceof BetterAutocomplete) {
- methods[method].call(this, bac);
- }
- break;
- default:
- $.error(['Method', method,
- 'does not exist in jQuery.betterAutocomplete.'].join(' '));
- }
- });
- // Maintain chainability
- return this;
- };
- /**
- * The BetterAutocomplete constructor function. Returns a BetterAutocomplete
- * instance object.
- *
- * @private @constructor
- * @name BetterAutocomplete
- *
- * @param {Object} $input
- * A single input element wrapped in jQuery.
- */
- var BetterAutocomplete = function($input, resource, options, callbacks) {
- var lastRenderedQuery = '',
- cache = {}, // Key-valued caching of search results
- cacheOrder = [], // Array of query strings, in the order they are added
- cacheSize = 0, // Keep count of the cache's size
- timer, // Used for options.delay
- activeRemoteCalls = [], // A flat array of query strings that are pending
- disableMouseHighlight = false, // Suppress the autotriggered mouseover event
- inputEvents = {},
- isLocal = ($.type(resource) != 'string'),
- $results = $('<ul />').addClass('better-autocomplete'),
- $ariaLive = null,
- hiddenResults = true, // $results are hidden
- preventBlurTimer = null; // IE bug workaround, see below in code.
- options = $.extend({
- charLimit: isLocal ? 1 : 3,
- delay: 350, // milliseconds
- caseSensitive: false,
- cacheLimit: isLocal ? 0 : 256, // Number of result objects
- remoteTimeout: 10000, // milliseconds
- crossOrigin: false,
- selectKeys: [9, 13], // [tab, enter]
- autoHighlight: true // Automatically highlight the topmost result
- }, options);
- callbacks = $.extend({}, defaultCallbacks, callbacks);
- callbacks.insertSuggestionList($results, $input);
- inputEvents.focus = function() {
- // If the blur timer is active, a redraw is redundant.
- preventBlurTimer || redraw(true);
- };
- inputEvents.blur = function() {
- // If the blur prevention timer is active, refocus the input, since the
- // blur event can not be cancelled.
- if (preventBlurTimer) {
- $input.focus();
- }
- else {
- // The input has already lost focus, so redraw the suggestion list.
- redraw();
- }
- };
- inputEvents.keydown = function(event) {
- var index = getHighlightedIndex();
- // If an arrow key is pressed and a result is highlighted
- if ($.inArray(event.keyCode, [38, 40]) >= 0 && $results.children().length > 0) {
- var newIndex,
- size = $('.result', $results).length;
- switch (event.keyCode) {
- case 38: // Up arrow
- newIndex = Math.max(-1, index - 1);
- break;
- case 40: // Down arrow
- newIndex = Math.min(size - 1, index + 1);
- break;
- }
- disableMouseHighlight = true;
- setHighlighted(newIndex, 'key', true);
- return false;
- }
- // A select key has been pressed
- else if ($.inArray(event.keyCode, options.selectKeys) >= 0 &&
- !event.shiftKey && !event.ctrlKey && !event.altKey &&
- !event.metaKey) {
- select();
- return event.keyCode == 9; // Never cancel tab
- }
- };
- inputEvents.keyup = inputEvents.click = reprocess;
- $results.delegate('.result', {
- // When the user hovers a result with the mouse, highlight it.
- mouseenter: function() {
- if (disableMouseHighlight) {
- return;
- }
- setHighlighted($('.result', $results).index($(this)), 'mouse');
- },
- mousemove: function() {
- // Enable mouseover again.
- disableMouseHighlight = false;
- },
- mousedown: function() {
- select();
- return false;
- }
- });
- // Prevent blur when clicking on group titles, scrollbars etc.,
- // This event is triggered after the others because of bubbling.
- $results.mousedown(function() {
- // Bug in IE where clicking on scrollbar would trigger a blur event for the
- // input field, despite using preventDefault() on the mousedown event.
- // This workaround locks the blur event on the input for a small time.
- clearTimeout(preventBlurTimer);
- preventBlurTimer = setTimeout(function() {
- preventBlurTimer = null;
- }, 50);
- return false;
- });
- // If auto highlight is off, remove highlighting
- $results.mouseleave(function() {
- if (!options.autoHighlight) {
- setHighlighted(-1);
- }
- });
- /*
- * PUBLIC METHODS
- */
- /**
- * Enable this instance.
- */
- this.enable = function() {
- // Turn off the browser's autocompletion
- $input
- .attr('autocomplete', 'OFF')
- .attr('aria-autocomplete', 'list')
- .parent()
- .attr('role', 'application')
- .append($('<span class="better-autocomplete-aria"></span>').attr({
- 'aria-live': 'assertive',
- 'id': $input.attr('id') + '-autocomplete-aria-live'
- }));
- $input.bind(inputEvents);
- $ariaLive = $('#' + $input.attr('id') + '-autocomplete-aria-live');
- };
- /**
- * Disable this instance.
- */
- this.disable = function() {
- $input
- .removeAttr('autocomplete')
- .removeAttr('aria-autocomplete')
- .parent()
- .removeAttr('role');
- $results.hide();
- $ariaLive.empty();
- $input.unbind(inputEvents);
- };
- /**
- * Disable and remove this instance. This instance should not be reused.
- */
- this.destroy = function() {
- $results.remove();
- $ariaLive.remove();
- $input.unbind(inputEvents);
- $input.removeData('better-autocomplete');
- };
- /*
- * PRIVATE METHODS
- */
- /**
- * Add an array of results to the cache. Internal methods always reads from
- * the cache, so this method must be invoked even when caching is not used,
- * e.g. when using local results. This method automatically clears as much of
- * the cache as required to fit within the cache limit.
- *
- * @param {String} query
- * The query to set the results to.
- *
- * @param {Array[Object]} results
- * The array of results for this query.
- */
- var cacheResults = function(query, results) {
- cacheSize += results.length;
- // Now reduce size until it fits
- while (cacheSize > options.cacheLimit && cacheOrder.length) {
- var key = cacheOrder.shift();
- cacheSize -= cache[key].length;
- delete cache[key];
- }
- cacheOrder.push(query);
- cache[query] = results;
- };
- /**
- * Set highlight to a specific result item
- *
- * @param {Number} index
- * The result item's index, or negative if highlight should be removed.
- *
- * @param {String} [trigger]
- * What triggered the highlight: "mouse", "key" or "auto". If index is
- * negative trigger may be omitted.
- *
- * @param {Boolean} [autoScroll]
- * (default=false) If scrolling of the results list should be automated.
- */
- var setHighlighted = function(index, trigger, autoScroll) {
- //console.log('Index: '+index)
- var prevIndex = getHighlightedIndex(),
- $resultList = $('.result', $results);
- //console.log('prevIndex: '+prevIndex)
- $resultList.removeClass('highlight');
- if (index < 0) {
- return
- }
- $resultList.eq(index).addClass('highlight')
- if (prevIndex != index) {
- $ariaLive.html($resultList.eq(index).html());
- var result = getResultByIndex(index);
- callbacks.highlight(result, $input, trigger);
- }
- // Scrolling
- var up = index == 0 || index < prevIndex,
- $scrollTo = $resultList.eq(index);
- if (!autoScroll) {
- return;
- }
- // Scrolling up, then make sure to show the group title
- if ($scrollTo.prev().is('.group') && up) {
- $scrollTo = $scrollTo.prev();
- }
- // Is $scrollTo partly above the visible region?
- if ($scrollTo.position().top < 0) {
- $results.scrollTop($scrollTo.position().top + $results.scrollTop());
- }
- // Or is it partly below the visible region?
- else if (($scrollTo.position().top + $scrollTo.outerHeight()) >
- $results.height()) {
- $results.scrollTop($scrollTo.position().top + $results.scrollTop() +
- $scrollTo.outerHeight() - $results.height());
- }
- };
- /**
- * Retrieve the index of the currently highlighted result item
- *
- * @returns {Number}
- * The result's index or -1 if no result is highlighted.
- */
- var getHighlightedIndex = function() {
- var res = $('.result.highlight', $results)
- ind= $('.result', $results).index(res);
- return ind
- };
- /**
- * Retrieve the result object with the specific position in the results list
- *
- * @param {Number} index
- * The index of the item in the current result list.
- *
- * @returns {Object}
- * The result object or null if index out of bounds.
- */
- var getResultByIndex = function(index) {
- var $result = $('.result', $results).eq(index);
- if (!$result.length) {
- return; // No selectable element
- }
- return $result.data('result');
- };
- /**
- * Select the current highlighted element, if any.
- */
- var select = function() {
- var highlighted = getHighlightedIndex();
- if(highlighted >= 0){
- var result = getResultByIndex(highlighted);
- callbacks.select(result, $input);
- // Redraw again, if the callback changed focus or content
- reprocess();
- }
- };
- /**
- * Fetch results asynchronously via AJAX.
- * Errors are ignored.
- *
- * @param {String} query
- * The query string.
- */
- var fetchResults = function(query) {
- // Synchronously fetch local data
- if (isLocal) {
- cacheResults(query, callbacks.queryLocalResults(query, resource,
- options.caseSensitive));
- redraw();
- }
- // Asynchronously fetch remote data
- else {
- activeRemoteCalls.push(query);
- var url = callbacks.constructURL(resource, query);
- $ariaLive.html('Searching for matches...');
- callbacks.beginFetching($input);
- callbacks.fetchRemoteData(url, function(data) {
- var searchResults = callbacks.processRemoteData(data);
- if (!$.isArray(searchResults)) {
- searchResults = [];
- }
- cacheResults(query, searchResults);
- // Remove the query from active remote calls, since it's finished
- activeRemoteCalls = $.grep(activeRemoteCalls, function(value) {
- return value != query;
- });
- if (!activeRemoteCalls.length) {
- callbacks.finishFetching($input);
- }
- redraw();
- }, options.remoteTimeout, options.crossOrigin);
- }
- };
- /**
- * Reprocess the contents of the input field, fetch data and redraw if
- * necessary.
- *
- * @param {Object} [event]
- * The event that triggered the reprocessing. Not always present.
- */
- function reprocess(event) {
- // If this call was triggered by an arrow key, cancel the reprocessing.
- if ($.type(event) == 'object' && event.type == 'keyup' &&
- $.inArray(event.keyCode, [38, 40]) >= 0) {
- return;
- }
- var query = callbacks.canonicalQuery($input.val(), options.caseSensitive);
- clearTimeout(timer);
- // Indicate that timer is inactive
- timer = null;
- redraw();
- if (query.length >= options.charLimit && !$.isArray(cache[query]) &&
- $.inArray(query, activeRemoteCalls) == -1) {
- // Fetching is required
- $results.empty();
- if (isLocal) {
- fetchResults(query);
- }
- else {
- timer = setTimeout(function() {
- fetchResults(query);
- timer = null;
- }, options.delay);
- }
- }
- };
- /**
- * Redraws the autocomplete list based on current query and focus.
- *
- * @param {Boolean} [focus]
- * (default=false) Force to treat the input element like it's focused.
- */
- var redraw = function(focus) {
- var query = callbacks.canonicalQuery($input.val(), options.caseSensitive);
- // The query does not exist in db
- if (!$.isArray(cache[query])) {
- lastRenderedQuery = null;
- $results.empty();
- }
- // The query exists and is not already rendered
- else if (lastRenderedQuery !== query) {
- lastRenderedQuery = query;
- renderResults(cache[query]);
- }
- // Finally show/hide based on focus and emptiness
- if (($input.is(':focus') || focus) && !$results.is(':empty')) {
- $results.filter(':hidden').show() // Show if hidden
- .scrollTop($results.data('scroll-top')); // Reset the lost scrolling
- if (hiddenResults) {
- hiddenResults = false;
- $ariaLive.html('Autocomplete popup');
- if (options.autoHighlight && $('.result', $results).length > 0) {
- setHighlighted(0, 'auto');
- }
- callbacks.afterShow($results);
- }
- }
- else if ($results.is(':visible')) {
- // Store the scrolling position for later
- $results.data('scroll-top', $results.scrollTop())
- .hide(); // Hiding it resets it's scrollTop
- if (!hiddenResults) {
- hiddenResults = true;
- $ariaLive.empty();
- callbacks.afterHide($results);
- }
- }
- };
- /**
- * Regenerate the DOM content within the results list for a given set of
- * results. Heavy method, use only when necessary.
- *
- * @param {Array[Object]} results
- * An array of result objects to render.
- */
- var renderResults = function(results) {
- $results.empty();
- var groups = {}; // Key is the group name, value is the heading element.
- $.each(results, function(index, result) {
- if ($.type(result) != 'object') {
- return; // Continue
- }
- var output = callbacks.themeResult(result);
- if ($.type(output) != 'string') {
- return; // Continue
- }
- // Add the group if it doesn't exist
- var group = callbacks.getGroup(result);
- if ($.type(group) == 'string' && !groups[group]) {
- var $groupHeading = $('<li />').addClass('group')
- .append($('<h3 />').html(group))
- .appendTo($results);
- groups[group] = $groupHeading;
- }
- var $result = $('<li />').addClass('result')
- .append(output)
- .data('result', result) // Store the result object on this DOM element
- .addClass(result.addClass);
- // First groupless item
- if ($.type(group) != 'string' &&
- !$results.children().first().is('.result')) {
- $results.prepend($result);
- return; // Continue
- }
- var $traverseFrom = ($.type(group) == 'string') ?
- groups[group] : $results.children().first();
- var $target = $traverseFrom.nextUntil('.group').last();
- $result.insertAfter($target.length ? $target : $traverseFrom);
- });
- };
- };
- /*
- * CALLBACK METHODS
- */
- /**
- * These callbacks are supposed to be overridden by you when you need
- * customization of the default behavior. When you are overriding a callback
- * function, it is a good idea to copy the source code from the default
- * callback function, as a skeleton.
- *
- * @name callbacks
- * @namespace
- */
- var defaultCallbacks = {
- /**
- * @lends callbacks.prototype
- */
- /**
- * Gets fired when the user selects a result by clicking or using the
- * keyboard to select an element.
- *
- * <br /><br /><em>Default behavior: Inserts the result's title into the
- * input field.</em>
- *
- * @param {Object} result
- * The result object that was selected.
- *
- * @param {Object} $input
- * The input DOM element, wrapped in jQuery.
- */
- select: function(result, $input) {
- $input.val(result.title);
- },
- /**
- * Gets fired when the a result is highlighted. This may happen either
- * automatically or by user action.
- *
- * <br /><br /><em>Default behavior: Does nothing.</em>
- *
- * @param {Object} result
- * The result object that was selected.
- *
- * @param {Object} $input
- * The input DOM element, wrapped in jQuery.
- *
- * @param {String} trigger
- * The event which triggered the highlighting. Must be one of the
- * following:
- * <ul><li>
- * "mouse": A mouseover event triggered the highlighting.
- * </li><li>
- * "key": The user pressed an arrow key to navigate amongst the results.
- * </li><li>
- * "auto": If options.autoHighlight is set, an automatic highlight of the
- * first result will occur each time a new result set is rendered.
- * </li></ul>
- */
- highlight: function(result, $input, trigger) {
- // Does nothing
- },
- /**
- * Given a result object, theme it to HTML.
- *
- * <br /><br /><em>Default behavior: Wraps result.title in an h4 tag, and
- * result.description in a p tag. Note that no sanitization of malicious
- * scripts is done here. Whatever is within the title/description is just
- * printed out. May contain HTML.</em>
- *
- * @param {Object} result
- * The result object that should be rendered.
- *
- * @returns {String}
- * HTML output, will be wrapped in a list element.
- */
- themeResult: function(result) {
- var output = [];
- if ($.type(result.title) == 'string') {
- output.push('<h4>', result.title, '</h4>');
- }
- if ($.type(result.description) == 'string') {
- output.push('<p>', result.description, '</p>');
- }
- return output.join('');
- },
- /**
- * Retrieve local results from the local resource by providing a query
- * string.
- *
- * <br /><br /><em>Default behavior: Automatically handles arrays, if the
- * data inside each element is either a plain string or a result object.
- * If it is a result object, it will match the query string against the
- * title and description property. Search is not case sensitive.</em>
- *
- * @param {String} query
- * The query string, unescaped. May contain any UTF-8 character.
- * If case insensitive, it already is lowercased.
- *
- * @param {Object} resource
- * The resource provided in the {@link jQuery.betterAutocomplete} init
- * constructor.
- *
- * @param {Boolean} caseSensitive
- * From options.caseSensitive, the searching should be case sensitive.
- *
- * @returns {Array[Object]}
- * A flat array containing pure result objects. May be an empty array.
- */
- queryLocalResults: function(query, resource, caseSensitive) {
- if (!$.isArray(resource)) {
- // Per default Better Autocomplete only handles arrays
- return [];
- }
- var results = [];
- $.each(resource, function(i, value) {
- switch ($.type(value)) {
- case 'string': // Flat array of strings
- if ((caseSensitive ? value : value.toLowerCase())
- .indexOf(query) >= 0) {
- // Match found
- results.push({ title: value });
- }
- break;
- case 'object': // Array of result objects
- if ($.type(value.title) == 'string' &&
- (caseSensitive ? value.title : value.title.toLowerCase())
- .indexOf(query) >= 0) {
- // Match found in title field
- results.push(value);
- }
- else if ($.type(value.description) == 'string' &&
- (caseSensitive ? value.description :
- value.description.toLowerCase()).indexOf(query) >= 0) {
- // Match found in description field
- results.push(value);
- }
- break;
- }
- });
- return results;
- },
- /**
- * Fetch remote result data and return it using completeCallback when
- * fetching is finished. Must be asynchronous in order to not freeze the
- * Better Autocomplete instance.
- *
- * <br /><br /><em>Default behavior: Fetches JSON data from the url, using
- * the jQuery.ajax() method. Errors are ignored.</em>
- *
- * @param {String} url
- * The URL to fetch data from.
- *
- * @param {Function} completeCallback
- * This function must be called, even if an error occurs. It takes zero
- * or one parameter: the data that was fetched.
- *
- * @param {Number} timeout
- * The preferred timeout for the request. This callback should respect
- * the timeout.
- *
- * @param {Boolean} crossOrigin
- * True if a cross origin request should be performed.
- */
- fetchRemoteData: function(url, completeCallback, timeout, crossOrigin) {
- $.ajax({
- url: url,
- dataType: crossOrigin && !$.support.cors ? 'jsonp' : 'json',
- timeout: timeout,
- success: function(data, textStatus) {
- completeCallback(data);
- },
- error: function(jqXHR, textStatus, errorThrown) {
- completeCallback();
- }
- });
- },
- /**
- * Process remote fetched data by extracting an array of result objects
- * from it. This callback is useful if the fetched data is not the plain
- * results array, but a more complicated object which does contain results.
- *
- * <br /><br /><em>Default behavior: If the data is defined and is an
- * array, return it. Otherwise return an empty array.</em>
- *
- * @param {mixed} data
- * The raw data recieved from the server. Can be undefined.
- *
- * @returns {Array[Object]}
- * A flat array containing result objects. May be an empty array.
- */
- processRemoteData: function(data) {
- if ($.isArray(data)) {
- return data;
- }
- else {
- return [];
- }
- },
- /**
- * From a given result object, return it's group name (if any). Used for
- * grouping results together.
- *
- * <br /><br /><em>Default behavior: If the result has a "group" property
- * defined, return it.</em>
- *
- * @param {Object} result
- * The result object.
- *
- * @returns {String}
- * The group name, may contain HTML. If no group, don't return anything.
- */
- getGroup: function(result) {
- if ($.type(result.group) == 'string') {
- return result.group;
- }
- },
- /**
- * Called when remote fetching begins.
- *
- * <br /><br /><em>Default behavior: Adds the CSS class "fetching" to the
- * input field, for styling purposes.</em>
- *
- * @param {Object} $input
- * The input DOM element, wrapped in jQuery.
- */
- beginFetching: function($input) {
- $input.addClass('fetching');
- },
- /**
- * Called when fetching is finished. All active requests must finish before
- * this function is called.
- *
- * <br /><br /><em>Default behavior: Removes the "fetching" class.</em>
- *
- * @param {Object} $input
- * The input DOM element, wrapped in jQuery.
- */
- finishFetching: function($input) {
- $input.removeClass('fetching');
- },
- /**
- * Executed after the suggestion list has been shown.
- *
- * @param {Object} $results
- * The suggestion list UL element, wrapped in jQuery.
- *
- * <br /><br /><em>Default behavior: Does nothing.</em>
- */
- afterShow: function($results) {},
- /**
- * Executed after the suggestion list has been hidden.
- *
- * @param {Object} $results
- * The suggestion list UL element, wrapped in jQuery.
- *
- * <br /><br /><em>Default behavior: Does nothing.</em>
- */
- afterHide: function($results) {},
- /**
- * Construct the remote fetching URL.
- *
- * <br /><br /><em>Default behavior: Adds "?q=<query>" or "&q=<query>" to the
- * path. The query string is URL encoded.</em>
- *
- * @param {String} path
- * The path given in the {@link jQuery.betterAutocomplete} constructor.
- *
- * @param {String} query
- * The raw query string. Remember to URL encode this to prevent illegal
- * character errors.
- *
- * @returns {String}
- * The URL, ready for fetching.
- */
- constructURL: function(path, query) {
- return path + (path.indexOf('?') > -1 ? '&' : '?') + 'q=' + encodeURIComponent(query);
- },
- /**
- * To ease up on server load, treat similar strings the same.
- *
- * <br /><br /><em>Default behavior: Trims the query from leading and
- * trailing whitespace.</em>
- *
- * @param {String} rawQuery
- * The user's raw input.
- *
- * @param {Boolean} caseSensitive
- * Case sensitive. Will convert to lowercase if false.
- *
- * @returns {String}
- * The canonical query associated with this string.
- */
- canonicalQuery: function(rawQuery, caseSensitive) {
- var query = $.trim(rawQuery);
- if (!caseSensitive) {
- query = query.toLowerCase();
- }
- return query;
- },
- /**
- * Insert the results list into the DOM and position it properly.
- *
- * <br /><br /><em>Default behavior: Inserts suggestion list directly
- * after the input element and sets an absolute position using
- * jQuery.position() for determining left/top values. Also adds a nice
- * looking box-shadow to the list.</em>
- *
- * @param {Object} $results
- * The UL list element to insert, wrapped in jQuery.
- *
- * @param {Object} $input
- * The text input element, wrapped in jQuery.
- */
- insertSuggestionList: function($results, $input) {
- $results.width($input.outerWidth() - 2) // Subtract border width.
- .css({
- position: 'absolute',
- left: $input.position().left,
- top: $input.position().top + $input.outerHeight()
- })
- .hide()
- .insertAfter($input);
- }
- };
- /*
- * jQuery focus selector, required by Better Autocomplete.
- *
- * @see http://stackoverflow.com/questions/967096/using-jquery-to-test-if-an-input-has-focus/2684561#2684561
- */
- var filters = $.expr[':'];
- if (!filters.focus) {
- filters.focus = function(elem) {
- return elem === document.activeElement && (elem.type || elem.href);
- };
- }
- })(jQuery);
|