diff --git a/.gitignore b/.gitignore
index acc0f65..f970be0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
/.idea*
/*.xml
/bower_components
+/node_modules/*
diff --git a/bower.json b/bower.json
index 984336b..5e71d8e 100644
--- a/bower.json
+++ b/bower.json
@@ -1,24 +1,24 @@
{
- "name": "devbridge-autocomplete",
- "version": "1.2.9",
- "homepage": "https://github.com/devbridge/jQuery-Autocomplete",
- "authors": [
- "Tomas Kirda"
- ],
- "description": "Autocomplete provides suggestions while you type into the text field.",
- "main": "dist/jquery.autocomplete.js",
- "keywords": [
- "ajax",
- "autocomplete"
- ],
- "license": "MIT",
- "ignore": [
- "**/.*",
- "node_modules",
- "bower_components",
- "spec"
- ],
- "dependencies": {
- "jquery": ">=1.7"
- }
-}
+ "name": "devbridge-autocomplete",
+ "version": "1.2.12",
+ "homepage": "https://github.com/devbridge/jQuery-Autocomplete",
+ "authors": [
+ "Tomas Kirda"
+ ],
+ "description": "Autocomplete provides suggestions while you type into the text field.",
+ "main": "dist/jquery.autocomplete.js",
+ "keywords": [
+ "ajax",
+ "autocomplete"
+ ],
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "spec"
+ ],
+ "dependencies": {
+ "jquery": ">=1.7"
+ }
+}
\ No newline at end of file
diff --git a/devbridge-autocomplete.jquery.json b/devbridge-autocomplete.jquery.json
index 89076e3..26c0f1d 100644
--- a/devbridge-autocomplete.jquery.json
+++ b/devbridge-autocomplete.jquery.json
@@ -6,7 +6,7 @@
"ajax",
"autocomplete"
],
- "version": "1.2.9",
+ "version": "1.2.12",
"author": {
"name": "Tomas Kirda",
"url": "https://github.com/tkirda"
@@ -20,7 +20,7 @@
"bugs": "https://github.com/devbridge/jQuery-Autocomplete/issues?state=open",
"homepage": "https://github.com/devbridge/jQuery-Autocomplete",
"docs": "https://github.com/devbridge/jQuery-Autocomplete",
- "demo": "http://www.devbridge.com/projects/autocomplete/jquery/",
+ "demo": "http://www.devbridge.com/sourcery/components/jquery-autocomplete/",
"download": "https://github.com/devbridge/jQuery-Autocomplete/tree/master/dist",
"dependencies": {
"jquery": ">=1.7"
diff --git a/dist/jquery.autocomplete.js b/dist/jquery.autocomplete.js
index 88548d4..2a5029e 100644
--- a/dist/jquery.autocomplete.js
+++ b/dist/jquery.autocomplete.js
@@ -1,14 +1,13 @@
/**
-* Ajax Autocomplete for jQuery, version 1.2.9
-* (c) 2013 Tomas Kirda
+* Ajax Autocomplete for jQuery, version 1.2.12
+* (c) 2014 Tomas Kirda
*
* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
-*
*/
/*jslint browser: true, white: true, plusplus: true */
-/*global define, window, document, jQuery */
+/*global define, window, document, jQuery, exports */
// Expose plugin as an AMD module if AMD loader is present:
(function (factory) {
@@ -16,6 +15,9 @@
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
+ } else if (typeof exports === 'object' && typeof require === 'function') {
+ // Browserify
+ factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
@@ -53,8 +55,9 @@
var noop = function () { },
that = this,
defaults = {
+ ajaxSettings: {},
autoSelectFirst: false,
- appendTo: 'body',
+ appendTo: document.body,
serviceUrl: null,
lookup: null,
onSelect: null,
@@ -76,13 +79,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 +106,7 @@
that.onChange = null;
that.isLocal = false;
that.suggestionsContainer = null;
+ that.noSuggestionsContainer = null;
that.options = $.extend({}, defaults, options);
that.classes = {
selected: 'autocomplete-selected',
@@ -143,6 +152,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 +183,6 @@
that.select($(this).data('index'));
});
- that.fixPosition();
-
that.fixPositionCapture = function () {
if (that.visible) {
that.fixPosition();
@@ -211,6 +222,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 +232,7 @@
});
},
+
clearCache: function () {
this.cachedResponse = {};
this.badQueries = [];
@@ -243,27 +257,65 @@
},
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(),
+ topOverflow = -scrollTop + offset.top - containerHeight,
+ bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
+
+ orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow)
+ ? 'top'
+ : '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 () {
@@ -396,7 +448,7 @@
query = that.getQuery(value),
index;
- if (that.selection) {
+ if (that.selection && that.currentValue !== query) {
that.selection = null;
(options.onInvalidateSelection || $.noop).call(that.element);
}
@@ -473,11 +525,12 @@
that = this,
options = that.options,
serviceUrl = options.serviceUrl,
- data,
- cacheKey;
+ params,
+ cacheKey,
+ ajaxSettings;
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 +538,7 @@
if ($.isFunction(serviceUrl)) {
serviceUrl = serviceUrl.call(that.element, q);
}
- cacheKey = serviceUrl + '?' + $.param(data || {});
+ cacheKey = serviceUrl + '?' + $.param(params || {});
response = that.cachedResponse[cacheKey];
}
@@ -499,15 +552,22 @@
if (that.currentRequest) {
that.currentRequest.abort();
}
- that.currentRequest = $.ajax({
+
+ ajaxSettings = {
url: serviceUrl,
- data: data,
+ data: params,
type: options.type,
dataType: options.dataType
- }).done(function (data) {
+ };
+
+ $.extend(ajaxSettings, options.ajaxSettings);
+
+ that.currentRequest = $.ajax(ajaxSettings).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 +575,10 @@
},
isBadQuery: function (q) {
+ if (!this.options.preventBadQueries){
+ return false;
+ }
+
var badQueries = this.badQueries,
i = badQueries.length;
@@ -537,7 +601,7 @@
suggest: function () {
if (this.suggestions.length === 0) {
- this.hide();
+ this.options.showNoSuggestionNotice ? this.noSuggestions() : this.hide();
return;
}
@@ -548,10 +612,10 @@
className = that.classes.suggestion,
classSelected = that.classes.selected,
container = $(that.suggestionsContainer),
+ noSuggestionsContainer = $(that.noSuggestionsContainer),
beforeRender = options.beforeRender,
html = '',
- index,
- width;
+ index;
if (options.triggerSelectOnValidInput) {
index = that.findSuggestionIndex(value);
@@ -566,15 +630,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 +645,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 +732,27 @@
return suggestions;
},
- processResponse: function (response, originalQuery, cacheKey) {
+ validateOrientation: function(orientation, fallback) {
+ orientation = $.trim(orientation || '').toLowerCase();
+
+ if($.inArray(orientation, ['auto', 'bottom', 'top']) === -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);
}
}
@@ -666,9 +770,9 @@
activeItem,
selected = that.classes.selected,
container = $(that.suggestionsContainer),
- children = container.children();
+ children = container.find('.' + that.classes.suggestion);
- container.children('.' + selected).removeClass(selected);
+ container.find('.' + selected).removeClass(selected);
that.selectedIndex = index;
@@ -754,7 +858,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 +902,7 @@
};
// Create chainable jQuery plugin:
- $.fn.autocomplete = function (options, args) {
+ $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
var dataKey = 'autocomplete';
// If function invoked without argument return
// instance of the first matched element:
diff --git a/dist/jquery.autocomplete.min.js b/dist/jquery.autocomplete.min.js
index 57b3b8d..f11e494 100644
--- a/dist/jquery.autocomplete.min.js
+++ b/dist/jquery.autocomplete.min.js
@@ -1,29 +1,8 @@
-/**
-* Ajax Autocomplete for jQuery, version 1.2.9
-* (c) 2013 Tomas Kirda
+/**
+* Ajax Autocomplete for jQuery, version 1.2.12
+* (c) 2014 Tomas Kirda
*
* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
-*
*/
-(function(d){"function"===typeof define&&define.amd?define(["jquery"],d):d(jQuery)})(function(d){function g(a,b){var c=function(){},c={autoSelectFirst:!1,appendTo:"body",serviceUrl:null,lookup:null,onSelect:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:g.formatResult,delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:c,onSearchComplete:c,onSearchError:c,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",currentRequest:null,triggerSelectOnValidInput:!0,
-lookupFilter:function(a,b,c){return-1!==a.value.toLowerCase().indexOf(c)},paramName:"query",transformResult:function(a){return"string"===typeof a?d.parseJSON(a):a}};this.element=a;this.el=d(a);this.suggestions=[];this.badQueries=[];this.selectedIndex=-1;this.currentValue=this.element.value;this.intervalId=0;this.cachedResponse={};this.onChange=this.onChangeInterval=null;this.isLocal=!1;this.suggestionsContainer=null;this.options=d.extend({},c,b);this.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"};
-this.hint=null;this.hintValue="";this.selection=null;this.initialize();this.setOptions(b)}var k=function(){return{escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},createNode:function(a){var b=document.createElement("div");b.className=a;b.style.position="absolute";b.style.display="none";return b}}}();g.utils=k;d.Autocomplete=g;g.formatResult=function(a,b){var c="("+k.escapeRegExChars(b)+")";return a.value.replace(RegExp(c,"gi"),"$1")};g.prototype=
-{killerFn:null,initialize:function(){var a=this,b="."+a.classes.suggestion,c=a.classes.selected,e=a.options,f;a.element.setAttribute("autocomplete","off");a.killerFn=function(b){0===d(b.target).closest("."+a.options.containerClass).length&&(a.killSuggestions(),a.disableKillerFn())};a.suggestionsContainer=g.utils.createNode(e.containerClass);f=d(a.suggestionsContainer);f.appendTo(e.appendTo);"auto"!==e.width&&f.width(e.width);f.on("mouseover.autocomplete",b,function(){a.activate(d(this).data("index"))});
-f.on("mouseout.autocomplete",function(){a.selectedIndex=-1;f.children("."+c).removeClass(c)});f.on("click.autocomplete",b,function(){a.select(d(this).data("index"))});a.fixPosition();a.fixPositionCapture=function(){a.visible&&a.fixPosition()};d(window).on("resize.autocomplete",a.fixPositionCapture);a.el.on("keydown.autocomplete",function(b){a.onKeyPress(b)});a.el.on("keyup.autocomplete",function(b){a.onKeyUp(b)});a.el.on("blur.autocomplete",function(){a.onBlur()});a.el.on("focus.autocomplete",function(){a.onFocus()});
-a.el.on("change.autocomplete",function(b){a.onKeyUp(b)})},onFocus:function(){this.fixPosition();if(this.options.minChars<=this.el.val().length)this.onValueChange()},onBlur:function(){this.enableKillerFn()},setOptions:function(a){var b=this.options;d.extend(b,a);if(this.isLocal=d.isArray(b.lookup))b.lookup=this.verifySuggestionsFormat(b.lookup);d(this.suggestionsContainer).css({"max-height":b.maxHeight+"px",width:b.width+"px","z-index":b.zIndex})},clearCache:function(){this.cachedResponse={};this.badQueries=
-[]},clear:function(){this.clearCache();this.currentValue="";this.suggestions=[]},disable:function(){this.disabled=!0;this.currentRequest&&this.currentRequest.abort()},enable:function(){this.disabled=!1},fixPosition:function(){var a;"body"===this.options.appendTo&&(a=this.el.offset(),a={top:a.top+this.el.outerHeight()+"px",left:a.left+"px"},"auto"===this.options.width&&(a.width=this.el.outerWidth()-2+"px"),d(this.suggestionsContainer).css(a))},enableKillerFn:function(){d(document).on("click.autocomplete",
-this.killerFn)},disableKillerFn:function(){d(document).off("click.autocomplete",this.killerFn)},killSuggestions:function(){var a=this;a.stopKillSuggestions();a.intervalId=window.setInterval(function(){a.hide();a.stopKillSuggestions()},50)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},isCursorAtEnd:function(){var a=this.el.val().length,b=this.element.selectionStart;return"number"===typeof b?b===a:document.selection?(b=document.selection.createRange(),b.moveStart("character",
--a),a===b.text.length):!0},onKeyPress:function(a){if(!this.disabled&&!this.visible&&40===a.which&&this.currentValue)this.suggest();else if(!this.disabled&&this.visible){switch(a.which){case 27:this.el.val(this.currentValue);this.hide();break;case 39:if(this.hint&&this.options.onHint&&this.isCursorAtEnd()){this.selectHint();break}return;case 9:if(this.hint&&this.options.onHint){this.selectHint();return}case 13:if(-1===this.selectedIndex){this.hide();return}this.select(this.selectedIndex);if(9===a.which&&
-!1===this.options.tabDisabled)return;break;case 38:this.moveUp();break;case 40:this.moveDown();break;default:return}a.stopImmediatePropagation();a.preventDefault()}},onKeyUp:function(a){var b=this;if(!b.disabled){switch(a.which){case 38:case 40:return}clearInterval(b.onChangeInterval);if(b.currentValue!==b.el.val())if(b.findBestHint(),0f&&(b.suggestions=b.suggestions.slice(0,f));return b},getSuggestions:function(a){var b,c=this,e=c.options,f=e.serviceUrl,l,g;e.params[e.paramName]=a;l=e.ignoreParams?null:e.params;
-c.isLocal?b=c.getSuggestionsLocal(a):(d.isFunction(f)&&(f=f.call(c.element,a)),g=f+"?"+d.param(l||{}),b=c.cachedResponse[g]);b&&d.isArray(b.suggestions)?(c.suggestions=b.suggestions,c.suggest()):c.isBadQuery(a)||!1===e.onSearchStart.call(c.element,e.params)||(c.currentRequest&&c.currentRequest.abort(),c.currentRequest=d.ajax({url:f,data:l,type:e.type,dataType:e.dataType}).done(function(b){c.currentRequest=null;c.processResponse(b,a,g);e.onSearchComplete.call(c.element,a)}).fail(function(b,d,f){e.onSearchError.call(c.element,
-a,b,d,f)}))},isBadQuery:function(a){for(var b=this.badQueries,c=b.length;c--;)if(0===a.indexOf(b[c]))return!0;return!1},hide:function(){this.visible=!1;this.selectedIndex=-1;d(this.suggestionsContainer).hide();this.signalHint(null)},suggest:function(){if(0===this.suggestions.length)this.hide();else{var a=this.options,b=a.formatResult,c=this.getQuery(this.currentValue),e=this.classes.suggestion,f=this.classes.selected,g=d(this.suggestionsContainer),k=a.beforeRender,m="",h;if(a.triggerSelectOnValidInput&&
-(h=this.findSuggestionIndex(c),-1!==h)){this.select(h);return}d.each(this.suggestions,function(a,d){m+=''+b(d,c)+"
"});"auto"===a.width&&(h=this.el.outerWidth()-2,g.width(0this.selectedIndex?(a=e.get(this.selectedIndex),d(a).addClass(b),a):null},selectHint:function(){var a=d.inArray(this.hint,this.suggestions);
-this.select(a)},select:function(a){this.hide();this.onSelect(a)},moveUp:function(){-1!==this.selectedIndex&&(0===this.selectedIndex?(d(this.suggestionsContainer).children().first().removeClass(this.classes.selected),this.selectedIndex=-1,this.el.val(this.currentValue),this.findBestHint()):this.adjustScroll(this.selectedIndex-1))},moveDown:function(){this.selectedIndex!==this.suggestions.length-1&&this.adjustScroll(this.selectedIndex+1)},adjustScroll:function(a){var b=this.activate(a),c,e;b&&(b=b.offsetTop,
-c=d(this.suggestionsContainer).scrollTop(),e=c+this.options.maxHeight-25,be&&d(this.suggestionsContainer).scrollTop(b-this.options.maxHeight+25),this.el.val(this.getValue(this.suggestions[a].value)),this.signalHint(null))},onSelect:function(a){var b=this.options.onSelect;a=this.suggestions[a];this.currentValue=this.getValue(a.value);this.el.val(this.currentValue);this.signalHint(null);this.suggestions=[];this.selection=a;d.isFunction(b)&&b.call(this.element,
-a)},getValue:function(a){var b=this.options.delimiter,c;if(!b)return a;c=this.currentValue;b=c.split(b);return 1===b.length?a:c.substr(0,c.length-b[b.length-1].length)+a},dispose:function(){this.el.off(".autocomplete").removeData("autocomplete");this.disableKillerFn();d(window).off("resize.autocomplete",this.fixPositionCapture);d(this.suggestionsContainer).remove()}};d.fn.autocomplete=function(a,b){return 0===arguments.length?this.first().data("autocomplete"):this.each(function(){var c=d(this),e=
-c.data("autocomplete");if("string"===typeof a){if(e&&"function"===typeof e[a])e[a](b)}else e&&e.dispose&&e.dispose(),e=new g(this,a),c.data("autocomplete",e)})}});
\ No newline at end of file
+!function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports&&"function"==typeof require?require("jquery"):jQuery)}(function(a){"use strict";function b(c,d){var e=function(){},f=this,g={ajaxSettings:{},autoSelectFirst:!1,appendTo:document.body,serviceUrl:null,lookup:null,onSelect:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:b.formatResult,delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:e,onSearchComplete:e,onSearchError:e,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",currentRequest:null,triggerSelectOnValidInput:!0,preventBadQueries:!0,lookupFilter:function(a,b,c){return-1!==a.value.toLowerCase().indexOf(c)},paramName:"query",transformResult:function(b){return"string"==typeof b?a.parseJSON(b):b},showNoSuggestionNotice:!1,noSuggestionNotice:"No results",orientation:"bottom",forceFixPosition:!1};f.element=c,f.el=a(c),f.suggestions=[],f.badQueries=[],f.selectedIndex=-1,f.currentValue=f.element.value,f.intervalId=0,f.cachedResponse={},f.onChangeInterval=null,f.onChange=null,f.isLocal=!1,f.suggestionsContainer=null,f.noSuggestionsContainer=null,f.options=a.extend({},g,d),f.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"},f.hint=null,f.hintValue="",f.selection=null,f.initialize(),f.setOptions(d)}var c=function(){return{escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},createNode:function(a){var b=document.createElement("div");return b.className=a,b.style.position="absolute",b.style.display="none",b}}}(),d={ESC:27,TAB:9,RETURN:13,LEFT:37,UP:38,RIGHT:39,DOWN:40};b.utils=c,a.Autocomplete=b,b.formatResult=function(a,b){var d="("+c.escapeRegExChars(b)+")";return a.value.replace(new RegExp(d,"gi"),"$1")},b.prototype={killerFn:null,initialize:function(){var c,d=this,e="."+d.classes.suggestion,f=d.classes.selected,g=d.options;d.element.setAttribute("autocomplete","off"),d.killerFn=function(b){0===a(b.target).closest("."+d.options.containerClass).length&&(d.killSuggestions(),d.disableKillerFn())},d.noSuggestionsContainer=a('').html(this.options.noSuggestionNotice).get(0),d.suggestionsContainer=b.utils.createNode(g.containerClass),c=a(d.suggestionsContainer),c.appendTo(g.appendTo),"auto"!==g.width&&c.width(g.width),c.on("mouseover.autocomplete",e,function(){d.activate(a(this).data("index"))}),c.on("mouseout.autocomplete",function(){d.selectedIndex=-1,c.children("."+f).removeClass(f)}),c.on("click.autocomplete",e,function(){d.select(a(this).data("index"))}),d.fixPositionCapture=function(){d.visible&&d.fixPosition()},a(window).on("resize.autocomplete",d.fixPositionCapture),d.el.on("keydown.autocomplete",function(a){d.onKeyPress(a)}),d.el.on("keyup.autocomplete",function(a){d.onKeyUp(a)}),d.el.on("blur.autocomplete",function(){d.onBlur()}),d.el.on("focus.autocomplete",function(){d.onFocus()}),d.el.on("change.autocomplete",function(a){d.onKeyUp(a)})},onFocus:function(){var a=this;a.fixPosition(),a.options.minChars<=a.el.val().length&&a.onValueChange()},onBlur:function(){this.enableKillerFn()},setOptions:function(b){var c=this,d=c.options;a.extend(d,b),c.isLocal=a.isArray(d.lookup),c.isLocal&&(d.lookup=c.verifySuggestionsFormat(d.lookup)),d.orientation=c.validateOrientation(d.orientation,"bottom"),a(c.suggestionsContainer).css({"max-height":d.maxHeight+"px",width:d.width+"px","z-index":d.zIndex})},clearCache:function(){this.cachedResponse={},this.badQueries=[]},clear:function(){this.clearCache(),this.currentValue="",this.suggestions=[]},disable:function(){var a=this;a.disabled=!0,a.currentRequest&&a.currentRequest.abort()},enable:function(){this.disabled=!1},fixPosition:function(){var b=this,c=a(b.suggestionsContainer),d=c.parent().get(0);if(d===document.body||b.options.forceFixPosition){var e=b.options.orientation,f=c.outerHeight(),g=b.el.outerHeight(),h=b.el.offset(),i={top:h.top,left:h.left};if("auto"==e){var j=a(window).height(),k=a(window).scrollTop(),l=-k+h.top-f,m=k+j-(h.top+g+f);e=Math.max(l,m)===l?"top":"bottom"}if(i.top+="top"===e?-f:g,d!==document.body){var n,o=c.css("opacity");b.visible||c.css("opacity",0).show(),n=c.offsetParent().offset(),i.top-=n.top,i.left-=n.left,b.visible||c.css("opacity",o).hide()}"auto"===b.options.width&&(i.width=b.el.outerWidth()-2+"px"),c.css(i)}},enableKillerFn:function(){var b=this;a(document).on("click.autocomplete",b.killerFn)},disableKillerFn:function(){var b=this;a(document).off("click.autocomplete",b.killerFn)},killSuggestions:function(){var a=this;a.stopKillSuggestions(),a.intervalId=window.setInterval(function(){a.hide(),a.stopKillSuggestions()},50)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},isCursorAtEnd:function(){var a,b=this,c=b.el.val().length,d=b.element.selectionStart;return"number"==typeof d?d===c:document.selection?(a=document.selection.createRange(),a.moveStart("character",-c),c===a.text.length):!0},onKeyPress:function(a){var b=this;if(!b.disabled&&!b.visible&&a.which===d.DOWN&&b.currentValue)return void b.suggest();if(!b.disabled&&b.visible){switch(a.which){case d.ESC:b.el.val(b.currentValue),b.hide();break;case d.RIGHT:if(b.hint&&b.options.onHint&&b.isCursorAtEnd()){b.selectHint();break}return;case d.TAB:if(b.hint&&b.options.onHint)return void b.selectHint();case d.RETURN:if(-1===b.selectedIndex)return void b.hide();if(b.select(b.selectedIndex),a.which===d.TAB&&b.options.tabDisabled===!1)return;break;case d.UP:b.moveUp();break;case d.DOWN:b.moveDown();break;default:return}a.stopImmediatePropagation(),a.preventDefault()}},onKeyUp:function(a){var b=this;if(!b.disabled){switch(a.which){case d.UP:case d.DOWN:return}clearInterval(b.onChangeInterval),b.currentValue!==b.el.val()&&(b.findBestHint(),b.options.deferRequestBy>0?b.onChangeInterval=setInterval(function(){b.onValueChange()},b.options.deferRequestBy):b.onValueChange())}},onValueChange:function(){var b,c=this,d=c.options,e=c.el.val(),f=c.getQuery(e);return c.selection&&c.currentValue!==f&&(c.selection=null,(d.onInvalidateSelection||a.noop).call(c.element)),clearInterval(c.onChangeInterval),c.currentValue=e,c.selectedIndex=-1,d.triggerSelectOnValidInput&&(b=c.findSuggestionIndex(f),-1!==b)?void c.select(b):void(f.lengthh&&(c.suggestions=c.suggestions.slice(0,h)),c},getSuggestions:function(b){var c,d,e,f,g=this,h=g.options,i=h.serviceUrl;if(h.params[h.paramName]=b,d=h.ignoreParams?null:h.params,g.isLocal?c=g.getSuggestionsLocal(b):(a.isFunction(i)&&(i=i.call(g.element,b)),e=i+"?"+a.param(d||{}),c=g.cachedResponse[e]),c&&a.isArray(c.suggestions))g.suggestions=c.suggestions,g.suggest();else if(!g.isBadQuery(b)){if(h.onSearchStart.call(g.element,h.params)===!1)return;g.currentRequest&&g.currentRequest.abort(),f={url:i,data:d,type:h.type,dataType:h.dataType},a.extend(f,h.ajaxSettings),g.currentRequest=a.ajax(f).done(function(a){var c;g.currentRequest=null,c=h.transformResult(a),g.processResponse(c,b,e),h.onSearchComplete.call(g.element,b,c.suggestions)}).fail(function(a,c,d){h.onSearchError.call(g.element,b,a,c,d)})}},isBadQuery:function(a){if(!this.options.preventBadQueries)return!1;for(var b=this.badQueries,c=b.length;c--;)if(0===a.indexOf(b[c]))return!0;return!1},hide:function(){var b=this;b.visible=!1,b.selectedIndex=-1,a(b.suggestionsContainer).hide(),b.signalHint(null)},suggest:function(){if(0===this.suggestions.length)return void(this.options.showNoSuggestionNotice?this.noSuggestions():this.hide());var b,c=this,d=c.options,e=d.formatResult,f=c.getQuery(c.currentValue),g=c.classes.suggestion,h=c.classes.selected,i=a(c.suggestionsContainer),j=a(c.noSuggestionsContainer),k=d.beforeRender,l="";return d.triggerSelectOnValidInput&&(b=c.findSuggestionIndex(f),-1!==b)?void c.select(b):(a.each(c.suggestions,function(a,b){l+=''+e(b,f)+"
"}),this.adjustContainerWidth(),j.detach(),i.html(l),d.autoSelectFirst&&(c.selectedIndex=0,i.children().first().addClass(h)),a.isFunction(k)&&k.call(c.element,i),c.fixPosition(),i.show(),c.visible=!0,void c.findBestHint())},noSuggestions:function(){var b=this,c=a(b.suggestionsContainer),d=a(b.noSuggestionsContainer);this.adjustContainerWidth(),d.detach(),c.empty(),c.append(d),b.fixPosition(),c.show(),b.visible=!0},adjustContainerWidth:function(){var b,c=this,d=c.options,e=a(c.suggestionsContainer);"auto"===d.width&&(b=c.el.outerWidth()-2,e.width(b>0?b:300))},findBestHint:function(){var b=this,c=b.el.val().toLowerCase(),d=null;c&&(a.each(b.suggestions,function(a,b){var e=0===b.value.toLowerCase().indexOf(c);return e&&(d=b),!e}),b.signalHint(d))},signalHint:function(b){var c="",d=this;b&&(c=d.currentValue+b.value.substr(d.currentValue.length)),d.hintValue!==c&&(d.hintValue=c,d.hint=b,(this.options.onHint||a.noop)(c))},verifySuggestionsFormat:function(b){return b.length&&"string"==typeof b[0]?a.map(b,function(a){return{value:a,data:null}}):b},validateOrientation:function(b,c){return b=a.trim(b||"").toLowerCase(),-1===a.inArray(b,["auto","bottom","top"])&&(b=c),b},processResponse:function(a,b,c){var d=this,e=d.options;a.suggestions=d.verifySuggestionsFormat(a.suggestions),e.noCache||(d.cachedResponse[c]=a,e.preventBadQueries&&0===a.suggestions.length&&d.badQueries.push(b)),b===d.getQuery(d.currentValue)&&(d.suggestions=a.suggestions,d.suggest())},activate:function(b){var c,d=this,e=d.classes.selected,f=a(d.suggestionsContainer),g=f.find("."+d.classes.suggestion);return f.find("."+e).removeClass(e),d.selectedIndex=b,-1!==d.selectedIndex&&g.length>d.selectedIndex?(c=g.get(d.selectedIndex),a(c).addClass(e),c):null},selectHint:function(){var b=this,c=a.inArray(b.hint,b.suggestions);b.select(c)},select:function(a){var b=this;b.hide(),b.onSelect(a)},moveUp:function(){var b=this;if(-1!==b.selectedIndex)return 0===b.selectedIndex?(a(b.suggestionsContainer).children().first().removeClass(b.classes.selected),b.selectedIndex=-1,b.el.val(b.currentValue),void b.findBestHint()):void b.adjustScroll(b.selectedIndex-1)},moveDown:function(){var a=this;a.selectedIndex!==a.suggestions.length-1&&a.adjustScroll(a.selectedIndex+1)},adjustScroll:function(b){var c,d,e,f=this,g=f.activate(b),h=25;g&&(c=g.offsetTop,d=a(f.suggestionsContainer).scrollTop(),e=d+f.options.maxHeight-h,d>c?a(f.suggestionsContainer).scrollTop(c):c>e&&a(f.suggestionsContainer).scrollTop(c-f.options.maxHeight+h),f.el.val(f.getValue(f.suggestions[b].value)),f.signalHint(null))},onSelect:function(b){var c=this,d=c.options.onSelect,e=c.suggestions[b];c.currentValue=c.getValue(e.value),c.currentValue!==c.el.val()&&c.el.val(c.currentValue),c.signalHint(null),c.suggestions=[],c.selection=e,a.isFunction(d)&&d.call(c.element,e)},getValue:function(a){var b,c,d=this,e=d.options.delimiter;return e?(b=d.currentValue,c=b.split(e),1===c.length?a:b.substr(0,b.length-c[c.length-1].length)+a):a},dispose:function(){var b=this;b.el.off(".autocomplete").removeData("autocomplete"),b.disableKillerFn(),a(window).off("resize.autocomplete",b.fixPositionCapture),a(b.suggestionsContainer).remove()}},a.fn.autocomplete=a.fn.devbridgeAutocomplete=function(c,d){var e="autocomplete";return 0===arguments.length?this.first().data(e):this.each(function(){var f=a(this),g=f.data(e);"string"==typeof c?g&&"function"==typeof g[c]&&g[c](d):(g&&g.dispose&&g.dispose(),g=new b(this,c),f.data(e,g))})}});
\ No newline at end of file
diff --git a/gruntfile.js b/gruntfile.js
new file mode 100644
index 0000000..ab394be
--- /dev/null
+++ b/gruntfile.js
@@ -0,0 +1,69 @@
+module.exports = function(grunt) {
+
+ var pkg = grunt.file.readJSON('package.json');
+
+ var banner = [
+ '/**',
+ '* Ajax Autocomplete for jQuery, version ' + pkg.version,
+ '* (c) 2014 Tomas Kirda',
+ '*',
+ '* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.',
+ '* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete',
+ '*/'].join('\n') + '\n';
+
+ // Project configuration.
+ grunt.initConfig({
+ pkg: pkg,
+ uglify: {
+ options: {
+ banner: banner
+ },
+ build: {
+ src: 'src/jquery.autocomplete.js',
+ dest: 'dist/jquery.autocomplete.min.js'
+ }
+ }
+ });
+
+ // Load the plugin that provides the "uglify" task.
+ grunt.loadNpmTasks('grunt-contrib-uglify');
+
+ // Default task(s).
+ grunt.registerTask('default', ['uglify']);
+
+ grunt.task.registerTask('build', 'Create release', function() {
+ var version = pkg.version
+ src = grunt.file.read('src/jquery.autocomplete.js').replace('%version%', version),
+ filePath = 'dist/jquery.autocomplete.js';
+
+ // Update not minimized release version:
+ console.log('Updating: ' + filePath);
+ grunt.file.write(filePath, src);
+
+ // Update plugin version:
+ filePath = 'devbridge-autocomplete.jquery.json';
+ src = grunt.file.readJSON(filePath);
+
+ if (src.version !== version){
+ src.version = version;
+ console.log('Updating: ' + filePath);
+ grunt.file.write(filePath, JSON.stringify(src, null, 4));
+ } else {
+ console.log('No updates for: ' + filePath);
+ }
+
+ // Update bower version:
+ filePath = 'bower.json';
+ src = grunt.file.readJSON(filePath);
+
+ if (src.version !== version){
+ src.version = version;
+ console.log('Updating: ' + filePath);
+ grunt.file.write(filePath, JSON.stringify(src, null, 4));
+ } else {
+ console.log('No updates for: ' + filePath);
+ }
+
+
+ });
+};
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8ebbb5b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "devbridge-autocomplete",
+ "version": "1.2.12",
+ "description": "Autocomplete provides suggestions while you type into the text field.",
+ "homepage": "https://github.com/devbridge/jQuery-Autocomplete",
+ "author": "Tomas Kirda (https://twitter.com/tkirda)",
+ "main": "dist/jquery.autocomplete.js",
+ "license": "MIT",
+ "dependencies": {
+ "jquery": ">=1.7"
+ },
+ "devDependencies": {
+ "grunt": "^0.4.5",
+ "grunt-contrib-uglify": "^0.5.1"
+ }
+}
diff --git a/readme.md b/readme.md
index 38a416a..662eed4 100644
--- a/readme.md
+++ b/readme.md
@@ -13,6 +13,7 @@ The standard jquery.autocomplete.js file is around 2.7KB when minified via Closu
* Sets up autocomplete for input field(s).
* `options`: An object literal which defines the settings to use for the autocomplete plugin.
* `serviceUrl`: Server side URL or callback function that returns serviceUrl string. Optional if local lookup data is provided.
+ * `ajaxSettings`: Any additional [Ajax Settings](http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings) that configure the jQuery Ajax request.
* `lookup`: Lookup array for the suggestions. It may be array of strings or `suggestion` object literals.
* `suggestion`: An object literal with the following format: `{ value: 'string', data: any }`.
* `lookupFilter`: `function (suggestion, query, queryLowerCase) {}` filter function for local lookups. By default it does partial string match (case insensitive).
@@ -158,6 +159,11 @@ you can supply the "paramName" and "transformResult" options:
}
})
+##Known Issues
+
+If you use it with jQuery UI library it also has plugin named `autocomplete`. In this case you can use plugin alias `devbridgeAutocomplete`:
+
+ $('.autocomplete').devbridgeAutocomplete({ ... });
##License
diff --git a/scripts/demo.js b/scripts/demo.js
index ab887b8..ea03dff 100644
--- a/scripts/demo.js
+++ b/scripts/demo.js
@@ -47,7 +47,7 @@ $(function () {
});
// Initialize autocomplete with local lookup:
- $('#autocomplete').autocomplete({
+ $('#autocomplete').devbridgeAutocomplete({
lookup: countriesArray,
minChars: 0,
onSelect: function (suggestion) {
diff --git a/src/jquery.autocomplete.js b/src/jquery.autocomplete.js
index f06d0d0..91fa30d 100644
--- a/src/jquery.autocomplete.js
+++ b/src/jquery.autocomplete.js
@@ -1,14 +1,13 @@
/**
-* Ajax Autocomplete for jQuery, version 1.2.9
-* (c) 2013 Tomas Kirda
+* Ajax Autocomplete for jQuery, version %version%
+* (c) 2014 Tomas Kirda
*
* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
-*
*/
/*jslint browser: true, white: true, plusplus: true */
-/*global define, window, document, jQuery */
+/*global define, window, document, jQuery, exports */
// Expose plugin as an AMD module if AMD loader is present:
(function (factory) {
@@ -16,6 +15,9 @@
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
+ } else if (typeof exports === 'object' && typeof require === 'function') {
+ // Browserify
+ factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
@@ -53,6 +55,7 @@
var noop = function () { },
that = this,
defaults = {
+ ajaxSettings: {},
autoSelectFirst: false,
appendTo: document.body,
serviceUrl: null,
@@ -137,8 +140,7 @@
suggestionSelector = '.' + that.classes.suggestion,
selected = that.classes.selected,
options = that.options,
- container,
- noSuggestionsContainer;
+ container;
// Remove autocomplete attribute to prevent native suggestions:
that.element.setAttribute('autocomplete', 'off');
@@ -275,34 +277,37 @@
if (orientation == 'auto') {
var viewPortHeight = $(window).height(),
scrollTop = $(window).scrollTop(),
- top_overflow = -scrollTop + offset.top - containerHeight,
- bottom_overflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
+ topOverflow = -scrollTop + offset.top - containerHeight,
+ bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
- if (Math.max(top_overflow, bottom_overflow) === top_overflow)
- orientation = 'top';
- else
- orientation = 'bottom';
+ orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow)
+ ? 'top'
+ : 'bottom';
}
- if (orientation === 'top')
+ if (orientation === 'top') {
styles.top += -containerHeight;
- else
- styles.top += height;
+ } else {
+ styles.top += height;
+ }
// 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();
+
+ if (!that.visible){
+ $container.css('opacity', 0).show();
+ }
parentOffsetDiff = $container.offsetParent().offset();
styles.top -= parentOffsetDiff.top;
styles.left -= parentOffsetDiff.left;
- if (!that.visible)
+ if (!that.visible){
$container.css('opacity', opacity).hide();
+ }
}
// -2px to account for suggestions border.
@@ -443,7 +448,7 @@
query = that.getQuery(value),
index;
- if (that.selection) {
+ if (that.selection && that.currentValue !== query) {
that.selection = null;
(options.onInvalidateSelection || $.noop).call(that.element);
}
@@ -521,7 +526,8 @@
options = that.options,
serviceUrl = options.serviceUrl,
params,
- cacheKey;
+ cacheKey,
+ ajaxSettings;
options.params[options.paramName] = q;
params = options.ignoreParams ? null : options.params;
@@ -546,12 +552,17 @@
if (that.currentRequest) {
that.currentRequest.abort();
}
- that.currentRequest = $.ajax({
+
+ ajaxSettings = {
url: serviceUrl,
data: params,
type: options.type,
dataType: options.dataType
- }).done(function (data) {
+ };
+
+ $.extend(ajaxSettings, options.ajaxSettings);
+
+ that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
var result;
that.currentRequest = null;
result = options.transformResult(data);
@@ -604,8 +615,7 @@
noSuggestionsContainer = $(that.noSuggestionsContainer),
beforeRender = options.beforeRender,
html = '',
- index,
- width;
+ index;
if (options.triggerSelectOnValidInput) {
index = that.findSuggestionIndex(value);
@@ -723,10 +733,13 @@
},
validateOrientation: function(orientation, fallback) {
- orientation = orientation.trim().toLowerCase();
- if(['auto', 'bottom', 'top'].indexOf(orientation) == '-1')
+ orientation = $.trim(orientation || '').toLowerCase();
+
+ if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
orientation = fallback;
- return orientation
+ }
+
+ return orientation;
},
processResponse: function (result, originalQuery, cacheKey) {
@@ -757,9 +770,9 @@
activeItem,
selected = that.classes.selected,
container = $(that.suggestionsContainer),
- children = container.children();
+ children = container.find('.' + that.classes.suggestion);
- container.children('.' + selected).removeClass(selected);
+ container.find('.' + selected).removeClass(selected);
that.selectedIndex = index;
@@ -889,7 +902,7 @@
};
// Create chainable jQuery plugin:
- $.fn.autocomplete = function (options, args) {
+ $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
var dataKey = 'autocomplete';
// If function invoked without argument return
// instance of the first matched element: