From 3e7fb81cf3560550d93bec9a416a03d06e4ba3a7 Mon Sep 17 00:00:00 2001 From: Tomas Kirda Date: Mon, 18 Aug 2014 08:57:03 -0500 Subject: [PATCH] Update dist source. --- dist/jquery.autocomplete.js | 171 ++++++++++++++++++++++++++++-------- 1 file changed, 133 insertions(+), 38 deletions(-) diff --git a/dist/jquery.autocomplete.js b/dist/jquery.autocomplete.js index d8693fa..e19863d 100644 --- a/dist/jquery.autocomplete.js +++ b/dist/jquery.autocomplete.js @@ -54,7 +54,7 @@ that = this, defaults = { autoSelectFirst: false, - appendTo: 'body', + appendTo: document.body, serviceUrl: null, lookup: null, onSelect: null, @@ -76,13 +76,18 @@ dataType: 'text', currentRequest: null, triggerSelectOnValidInput: true, + preventBadQueries: true, lookupFilter: function (suggestion, originalQuery, queryLowerCase) { return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; }, paramName: 'query', transformResult: function (response) { return typeof response === 'string' ? $.parseJSON(response) : response; - } + }, + showNoSuggestionNotice: false, + noSuggestionNotice: 'No results', + orientation: 'bottom', + forceFixPosition: false }; // Shared variables: @@ -98,6 +103,7 @@ that.onChange = null; that.isLocal = false; that.suggestionsContainer = null; + that.noSuggestionsContainer = null; that.options = $.extend({}, defaults, options); that.classes = { selected: 'autocomplete-selected', @@ -131,7 +137,8 @@ suggestionSelector = '.' + that.classes.suggestion, selected = that.classes.selected, options = that.options, - container; + container, + noSuggestionsContainer; // Remove autocomplete attribute to prevent native suggestions: that.element.setAttribute('autocomplete', 'off'); @@ -143,6 +150,10 @@ } }; + // html() deals with many types: htmlString or Element or Array or jQuery + that.noSuggestionsContainer = $('
') + .html(this.options.noSuggestionNotice).get(0); + that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass); container = $(that.suggestionsContainer); @@ -170,8 +181,6 @@ that.select($(this).data('index')); }); - that.fixPosition(); - that.fixPositionCapture = function () { if (that.visible) { that.fixPosition(); @@ -211,6 +220,8 @@ options.lookup = that.verifySuggestionsFormat(options.lookup); } + options.orientation = that.validateOrientation(options.orientation, 'bottom'); + // Adjust height, width and z-index: $(that.suggestionsContainer).css({ 'max-height': options.maxHeight + 'px', @@ -219,6 +230,7 @@ }); }, + clearCache: function () { this.cachedResponse = {}; this.badQueries = []; @@ -243,27 +255,62 @@ }, fixPosition: function () { - var that = this, - offset, - styles; + // Use only when container has already its content - // Don't adjsut position if custom container has been specified: - if (that.options.appendTo !== 'body') { + var that = this, + $container = $(that.suggestionsContainer), + containerParent = $container.parent().get(0); + // Fix position automatically when appended to body. + // In other cases force parameter must be given. + if (containerParent !== document.body && !that.options.forceFixPosition) return; + + // Choose orientation + var orientation = that.options.orientation, + containerHeight = $container.outerHeight(), + height = that.el.outerHeight(), + offset = that.el.offset(), + styles = { 'top': offset.top, 'left': offset.left }; + + if (orientation == 'auto') { + var viewPortHeight = $(window).height(), + scrollTop = $(window).scrollTop(), + top_overflow = -scrollTop + offset.top - containerHeight, + bottom_overflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight); + + if (Math.max(top_overflow, bottom_overflow) === top_overflow) + orientation = 'top'; + else + orientation = 'bottom'; } - offset = that.el.offset(); + if (orientation === 'top') + styles.top += -containerHeight; + else + styles.top += height; - styles = { - top: (offset.top + that.el.outerHeight()) + 'px', - left: offset.left + 'px' - }; + // If container is not positioned to body, + // correct its position using offset parent offset + if(containerParent !== document.body) { + var opacity = $container.css('opacity'), + parentOffsetDiff; + if (!that.visible) + $container.css('opacity', 0).show(); + parentOffsetDiff = $container.offsetParent().offset(); + styles.top -= parentOffsetDiff.top; + styles.left -= parentOffsetDiff.left; + + if (!that.visible) + $container.css('opacity', opacity).hide(); + } + + // -2px to account for suggestions border. if (that.options.width === 'auto') { styles.width = (that.el.outerWidth() - 2) + 'px'; } - $(that.suggestionsContainer).css(styles); + $container.css(styles); }, enableKillerFn: function () { @@ -473,11 +520,11 @@ that = this, options = that.options, serviceUrl = options.serviceUrl, - data, + params, cacheKey; options.params[options.paramName] = q; - data = options.ignoreParams ? null : options.params; + params = options.ignoreParams ? null : options.params; if (that.isLocal) { response = that.getSuggestionsLocal(q); @@ -485,7 +532,7 @@ if ($.isFunction(serviceUrl)) { serviceUrl = serviceUrl.call(that.element, q); } - cacheKey = serviceUrl + '?' + $.param(data || {}); + cacheKey = serviceUrl + '?' + $.param(params || {}); response = that.cachedResponse[cacheKey]; } @@ -501,13 +548,15 @@ } that.currentRequest = $.ajax({ url: serviceUrl, - data: data, + data: params, type: options.type, dataType: options.dataType }).done(function (data) { + var result; that.currentRequest = null; - that.processResponse(data, q, cacheKey); - options.onSearchComplete.call(that.element, q); + result = options.transformResult(data); + that.processResponse(result, q, cacheKey); + options.onSearchComplete.call(that.element, q, result.suggestions); }).fail(function (jqXHR, textStatus, errorThrown) { options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown); }); @@ -515,6 +564,10 @@ }, isBadQuery: function (q) { + if (!this.options.preventBadQueries){ + return false; + } + var badQueries = this.badQueries, i = badQueries.length; @@ -537,7 +590,7 @@ suggest: function () { if (this.suggestions.length === 0) { - this.hide(); + this.options.showNoSuggestionNotice ? this.noSuggestions() : this.hide(); return; } @@ -548,6 +601,7 @@ className = that.classes.suggestion, classSelected = that.classes.selected, container = $(that.suggestionsContainer), + noSuggestionsContainer = $(that.noSuggestionsContainer), beforeRender = options.beforeRender, html = '', index, @@ -566,15 +620,9 @@ html += '
' + formatResult(suggestion, value) + '
'; }); - // If width is auto, adjust width before displaying suggestions, - // because if instance was created before input had width, it will be zero. - // Also it adjusts if input width has changed. - // -2px to account for suggestions border. - if (options.width === 'auto') { - width = that.el.outerWidth() - 2; - container.width(width > 0 ? width : 300); - } + this.adjustContainerWidth(); + noSuggestionsContainer.detach(); container.html(html); // Select first value by default: @@ -587,12 +635,49 @@ beforeRender.call(that.element, container); } + that.fixPosition(); + container.show(); that.visible = true; that.findBestHint(); }, + noSuggestions: function() { + var that = this, + container = $(that.suggestionsContainer), + noSuggestionsContainer = $(that.noSuggestionsContainer); + + this.adjustContainerWidth(); + + // Some explicit steps. Be careful here as it easy to get + // noSuggestionsContainer removed from DOM if not detached properly. + noSuggestionsContainer.detach(); + container.empty(); // clean suggestions if any + container.append(noSuggestionsContainer); + + that.fixPosition(); + + container.show(); + that.visible = true; + }, + + adjustContainerWidth: function() { + var that = this, + options = that.options, + width, + container = $(that.suggestionsContainer); + + // If width is auto, adjust width before displaying suggestions, + // because if instance was created before input had width, it will be zero. + // Also it adjusts if input width has changed. + // -2px to account for suggestions border. + if (options.width === 'auto') { + width = that.el.outerWidth() - 2; + container.width(width > 0 ? width : 300); + } + }, + findBestHint: function () { var that = this, value = that.el.val().toLowerCase(), @@ -637,18 +722,24 @@ return suggestions; }, - processResponse: function (response, originalQuery, cacheKey) { + validateOrientation: function(orientation, fallback) { + orientation = orientation.trim().toLowerCase(); + if(['auto', 'bottom', 'top'].indexOf(orientation) == '-1') + orientation = fallback; + return orientation + }, + + processResponse: function (result, originalQuery, cacheKey) { var that = this, - options = that.options, - result = options.transformResult(response, originalQuery); + options = that.options; result.suggestions = that.verifySuggestionsFormat(result.suggestions); // Cache results if cache is not disabled: if (!options.noCache) { that.cachedResponse[cacheKey] = result; - if (result.suggestions.length === 0) { - that.badQueries.push(cacheKey); + if (options.preventBadQueries && result.suggestions.length === 0) { + that.badQueries.push(originalQuery); } } @@ -754,7 +845,11 @@ suggestion = that.suggestions[index]; that.currentValue = that.getValue(suggestion.value); - that.el.val(that.currentValue); + + if (that.currentValue !== that.el.val()) { + that.el.val(that.currentValue); + } + that.signalHint(null); that.suggestions = []; that.selection = suggestion; @@ -794,7 +889,7 @@ }; // Create chainable jQuery plugin: - $.fn.autocomplete = function (options, args) { + $.fn.autocomplete = function (options, args) { var dataKey = 'autocomplete'; // If function invoked without argument return // instance of the first matched element: