From 6c019d2c0711da1c632b7759c18a999c1ca2637b Mon Sep 17 00:00:00 2001 From: Aigars Silkalns Date: Fri, 26 Feb 2016 17:54:22 +0200 Subject: [PATCH] Updated Ajax Autocomplete for jQuery --- production/js/autocomplete/countries.js | 268 +++++++++++++- .../js/autocomplete/jquery.autocomplete.js | 344 +++++++++++++----- 2 files changed, 512 insertions(+), 100 deletions(-) diff --git a/production/js/autocomplete/countries.js b/production/js/autocomplete/countries.js index c97a7673..7d41ee4e 100755 --- a/production/js/autocomplete/countries.js +++ b/production/js/autocomplete/countries.js @@ -1 +1,267 @@ -var countries={AD:"Andorra",AE:"United Arab Emirates",AF:"Afghanistan",AG:"Antigua and Barbuda",AI:"Anguilla",AL:"Albania",AM:"Armenia",AN:"Netherlands Antilles",AO:"Angola",AQ:"Antarctica",AR:"Argentina",AS:"American Samoa",AT:"Austria",AU:"Australia",AW:"Aruba",AX:"Åland Islands",AZ:"Azerbaijan",BA:"Bosnia and Herzegovina",BB:"Barbados",BD:"Bangladesh",BE:"Belgium",BF:"Burkina Faso",BG:"Bulgaria",BH:"Bahrain",BI:"Burundi",BJ:"Benin",BL:"Saint Barthélemy",BM:"Bermuda",BN:"Brunei",BO:"Bolivia",BQ:"British Antarctic Territory",BR:"Brazil",BS:"Bahamas",BT:"Bhutan",BV:"Bouvet Island",BW:"Botswana",BY:"Belarus",BZ:"Belize",CA:"Canada",CC:"Cocos [Keeling] Islands",CD:"Congo - Kinshasa",CF:"Central African Republic",CG:"Congo - Brazzaville",CH:"Switzerland",CI:"Côte d’Ivoire",CK:"Cook Islands",CL:"Chile",CM:"Cameroon",CN:"China",CO:"Colombia",CR:"Costa Rica",CS:"Serbia and Montenegro",CT:"Canton and Enderbury Islands",CU:"Cuba",CV:"Cape Verde",CX:"Christmas Island",CY:"Cyprus",CZ:"Czech Republic",DD:"East Germany",DE:"Germany",DJ:"Djibouti",DK:"Denmark",DM:"Dominica",DO:"Dominican Republic",DZ:"Algeria",EC:"Ecuador",EE:"Estonia",EG:"Egypt",EH:"Western Sahara",ER:"Eritrea",ES:"Spain",ET:"Ethiopia",FI:"Finland",FJ:"Fiji",FK:"Falkland Islands",FM:"Micronesia",FO:"Faroe Islands",FQ:"French Southern and Antarctic Territories",FR:"France",FX:"Metropolitan France",GA:"Gabon",GB:"United Kingdom",GD:"Grenada",GE:"Georgia",GF:"French Guiana",GG:"Guernsey",GH:"Ghana",GI:"Gibraltar",GL:"Greenland",GM:"Gambia",GN:"Guinea",GP:"Guadeloupe",GQ:"Equatorial Guinea",GR:"Greece",GS:"South Georgia and the South Sandwich Islands",GT:"Guatemala",GU:"Guam",GW:"Guinea-Bissau",GY:"Guyana",HK:"Hong Kong SAR China",HM:"Heard Island and McDonald Islands",HN:"Honduras",HR:"Croatia",HT:"Haiti",HU:"Hungary",ID:"Indonesia",IE:"Ireland",IL:"Israel",IM:"Isle of Man",IN:"India",IO:"British Indian Ocean Territory",IQ:"Iraq",IR:"Iran",IS:"Iceland",IT:"Italy",JE:"Jersey",JM:"Jamaica",JO:"Jordan",JP:"Japan",JT:"Johnston Island",KE:"Kenya",KG:"Kyrgyzstan",KH:"Cambodia",KI:"Kiribati",KM:"Comoros",KN:"Saint Kitts and Nevis",KP:"North Korea",KR:"South Korea",KW:"Kuwait",KY:"Cayman Islands",KZ:"Kazakhstan",LA:"Laos",LB:"Lebanon",LC:"Saint Lucia",LI:"Liechtenstein",LK:"Sri Lanka",LR:"Liberia",LS:"Lesotho",LT:"Lithuania",LU:"Luxembourg",LV:"Latvia",LY:"Libya",MA:"Morocco",MC:"Monaco",MD:"Moldova",ME:"Montenegro",MF:"Saint Martin",MG:"Madagascar",MH:"Marshall Islands",MI:"Midway Islands",MK:"Macedonia",ML:"Mali",MM:"Myanmar [Burma]",MN:"Mongolia",MO:"Macau SAR China",MP:"Northern Mariana Islands",MQ:"Martinique",MR:"Mauritania",MS:"Montserrat",MT:"Malta",MU:"Mauritius",MV:"Maldives",MW:"Malawi",MX:"Mexico",MY:"Malaysia",MZ:"Mozambique",NA:"Namibia",NC:"New Caledonia",NE:"Niger",NF:"Norfolk Island",NG:"Nigeria",NI:"Nicaragua",NL:"Netherlands",NO:"Norway",NP:"Nepal",NQ:"Dronning Maud Land",NR:"Nauru",NT:"Neutral Zone",NU:"Niue",NZ:"New Zealand",OM:"Oman",PA:"Panama",PC:"Pacific Islands Trust Territory",PE:"Peru",PF:"French Polynesia",PG:"Papua New Guinea",PH:"Philippines",PK:"Pakistan",PL:"Poland",PM:"Saint Pierre and Miquelon",PN:"Pitcairn Islands",PR:"Puerto Rico",PS:"Palestinian Territories",PT:"Portugal",PU:"U.S. Miscellaneous Pacific Islands",PW:"Palau",PY:"Paraguay",PZ:"Panama Canal Zone",QA:"Qatar",RE:"Réunion",RO:"Romania",RS:"Serbia",RU:"Russia",RW:"Rwanda",SA:"Saudi Arabia",SB:"Solomon Islands",SC:"Seychelles",SD:"Sudan",SE:"Sweden",SG:"Singapore",SH:"Saint Helena",SI:"Slovenia",SJ:"Svalbard and Jan Mayen",SK:"Slovakia",SL:"Sierra Leone",SM:"San Marino",SN:"Senegal",SO:"Somalia",SR:"Suriname",ST:"São Tomé and Príncipe",SU:"Union of Soviet Socialist Republics",SV:"El Salvador",SY:"Syria",SZ:"Swaziland",TC:"Turks and Caicos Islands",TD:"Chad",TF:"French Southern Territories",TG:"Togo",TH:"Thailand",TJ:"Tajikistan",TK:"Tokelau",TL:"Timor-Leste",TM:"Turkmenistan",TN:"Tunisia",TO:"Tonga",TR:"Turkey",TT:"Trinidad and Tobago",TV:"Tuvalu",TW:"Taiwan",TZ:"Tanzania",UA:"Ukraine",UG:"Uganda",UM:"U.S. Minor Outlying Islands",US:"United States",UY:"Uruguay",UZ:"Uzbekistan",VA:"Vatican City",VC:"Saint Vincent and the Grenadines",VD:"North Vietnam",VE:"Venezuela",VG:"British Virgin Islands",VI:"U.S. Virgin Islands",VN:"Vietnam",VU:"Vanuatu",WF:"Wallis and Futuna",WK:"Wake Island",WS:"Samoa",YD:"People's Democratic Republic of Yemen",YE:"Yemen",YT:"Mayotte",ZA:"South Africa",ZM:"Zambia",ZW:"Zimbabwe",ZZ:"Unknown or Invalid Region"} \ No newline at end of file +var countries = { + "AD": "Andorra", + "A2": "Andorra Test", + "AE": "United Arab Emirates", + "AF": "Afghanistan", + "AG": "Antigua and Barbuda", + "AI": "Anguilla", + "AL": "Albania", + "AM": "Armenia", + "AN": "Netherlands Antilles", + "AO": "Angola", + "AQ": "Antarctica", + "AR": "Argentina", + "AS": "American Samoa", + "AT": "Austria", + "AU": "Australia", + "AW": "Aruba", + "AX": "\u00c5land Islands", + "AZ": "Azerbaijan", + "BA": "Bosnia and Herzegovina", + "BB": "Barbados", + "BD": "Bangladesh", + "BE": "Belgium", + "BF": "Burkina Faso", + "BG": "Bulgaria", + "BH": "Bahrain", + "BI": "Burundi", + "BJ": "Benin", + "BL": "Saint Barth\u00e9lemy", + "BM": "Bermuda", + "BN": "Brunei", + "BO": "Bolivia", + "BQ": "British Antarctic Territory", + "BR": "Brazil", + "BS": "Bahamas", + "BT": "Bhutan", + "BV": "Bouvet Island", + "BW": "Botswana", + "BY": "Belarus", + "BZ": "Belize", + "CA": "Canada", + "CC": "Cocos [Keeling] Islands", + "CD": "Congo - Kinshasa", + "CF": "Central African Republic", + "CG": "Congo - Brazzaville", + "CH": "Switzerland", + "CI": "C\u00f4te d\u2019Ivoire", + "CK": "Cook Islands", + "CL": "Chile", + "CM": "Cameroon", + "CN": "China", + "CO": "Colombia", + "CR": "Costa Rica", + "CS": "Serbia and Montenegro", + "CT": "Canton and Enderbury Islands", + "CU": "Cuba", + "CV": "Cape Verde", + "CX": "Christmas Island", + "CY": "Cyprus", + "CZ": "Czech Republic", + "DD": "East Germany", + "DE": "Germany", + "DJ": "Djibouti", + "DK": "Denmark", + "DM": "Dominica", + "DO": "Dominican Republic", + "DZ": "Algeria", + "EC": "Ecuador", + "EE": "Estonia", + "EG": "Egypt", + "EH": "Western Sahara", + "ER": "Eritrea", + "ES": "Spain", + "ET": "Ethiopia", + "FI": "Finland", + "FJ": "Fiji", + "FK": "Falkland Islands", + "FM": "Micronesia", + "FO": "Faroe Islands", + "FQ": "French Southern and Antarctic Territories", + "FR": "France", + "FX": "Metropolitan France", + "GA": "Gabon", + "GB": "United Kingdom", + "GD": "Grenada", + "GE": "Georgia", + "GF": "French Guiana", + "GG": "Guernsey", + "GH": "Ghana", + "GI": "Gibraltar", + "GL": "Greenland", + "GM": "Gambia", + "GN": "Guinea", + "GP": "Guadeloupe", + "GQ": "Equatorial Guinea", + "GR": "Greece", + "GS": "South Georgia and the South Sandwich Islands", + "GT": "Guatemala", + "GU": "Guam", + "GW": "Guinea-Bissau", + "GY": "Guyana", + "HK": "Hong Kong SAR China", + "HM": "Heard Island and McDonald Islands", + "HN": "Honduras", + "HR": "Croatia", + "HT": "Haiti", + "HU": "Hungary", + "ID": "Indonesia", + "IE": "Ireland", + "IL": "Israel", + "IM": "Isle of Man", + "IN": "India", + "IO": "British Indian Ocean Territory", + "IQ": "Iraq", + "IR": "Iran", + "IS": "Iceland", + "IT": "Italy", + "JE": "Jersey", + "JM": "Jamaica", + "JO": "Jordan", + "JP": "Japan", + "JT": "Johnston Island", + "KE": "Kenya", + "KG": "Kyrgyzstan", + "KH": "Cambodia", + "KI": "Kiribati", + "KM": "Comoros", + "KN": "Saint Kitts and Nevis", + "KP": "North Korea", + "KR": "South Korea", + "KW": "Kuwait", + "KY": "Cayman Islands", + "KZ": "Kazakhstan", + "LA": "Laos", + "LB": "Lebanon", + "LC": "Saint Lucia", + "LI": "Liechtenstein", + "LK": "Sri Lanka", + "LR": "Liberia", + "LS": "Lesotho", + "LT": "Lithuania", + "LU": "Luxembourg", + "LV": "Latvia", + "LY": "Libya", + "MA": "Morocco", + "MC": "Monaco", + "MD": "Moldova", + "ME": "Montenegro", + "MF": "Saint Martin", + "MG": "Madagascar", + "MH": "Marshall Islands", + "MI": "Midway Islands", + "MK": "Macedonia", + "ML": "Mali", + "MM": "Myanmar [Burma]", + "MN": "Mongolia", + "MO": "Macau SAR China", + "MP": "Northern Mariana Islands", + "MQ": "Martinique", + "MR": "Mauritania", + "MS": "Montserrat", + "MT": "Malta", + "MU": "Mauritius", + "MV": "Maldives", + "MW": "Malawi", + "MX": "Mexico", + "MY": "Malaysia", + "MZ": "Mozambique", + "NA": "Namibia", + "NC": "New Caledonia", + "NE": "Niger", + "NF": "Norfolk Island", + "NG": "Nigeria", + "NI": "Nicaragua", + "NL": "Netherlands", + "NO": "Norway", + "NP": "Nepal", + "NQ": "Dronning Maud Land", + "NR": "Nauru", + "NT": "Neutral Zone", + "NU": "Niue", + "NZ": "New Zealand", + "OM": "Oman", + "PA": "Panama", + "PC": "Pacific Islands Trust Territory", + "PE": "Peru", + "PF": "French Polynesia", + "PG": "Papua New Guinea", + "PH": "Philippines", + "PK": "Pakistan", + "PL": "Poland", + "PM": "Saint Pierre and Miquelon", + "PN": "Pitcairn Islands", + "PR": "Puerto Rico", + "PS": "Palestinian Territories", + "PT": "Portugal", + "PU": "U.S. Miscellaneous Pacific Islands", + "PW": "Palau", + "PY": "Paraguay", + "PZ": "Panama Canal Zone", + "QA": "Qatar", + "RE": "R\u00e9union", + "RO": "Romania", + "RS": "Serbia", + "RU": "Russia", + "RW": "Rwanda", + "SA": "Saudi Arabia", + "SB": "Solomon Islands", + "SC": "Seychelles", + "SD": "Sudan", + "SE": "Sweden", + "SG": "Singapore", + "SH": "Saint Helena", + "SI": "Slovenia", + "SJ": "Svalbard and Jan Mayen", + "SK": "Slovakia", + "SL": "Sierra Leone", + "SM": "San Marino", + "SN": "Senegal", + "SO": "Somalia", + "SR": "Suriname", + "ST": "S\u00e3o Tom\u00e9 and Pr\u00edncipe", + "SU": "Union of Soviet Socialist Republics", + "SV": "El Salvador", + "SY": "Syria", + "SZ": "Swaziland", + "TC": "Turks and Caicos Islands", + "TD": "Chad", + "TF": "French Southern Territories", + "TG": "Togo", + "TH": "Thailand", + "TJ": "Tajikistan", + "TK": "Tokelau", + "TL": "Timor-Leste", + "TM": "Turkmenistan", + "TN": "Tunisia", + "TO": "Tonga", + "TR": "Turkey", + "TT": "Trinidad and Tobago", + "TV": "Tuvalu", + "TW": "Taiwan", + "TZ": "Tanzania", + "UA": "Ukraine", + "UG": "Uganda", + "UM": "U.S. Minor Outlying Islands", + "US": "United States", + "UY": "Uruguay", + "UZ": "Uzbekistan", + "VA": "Vatican City", + "VC": "Saint Vincent and the Grenadines", + "VD": "North Vietnam", + "VE": "Venezuela", + "VG": "British Virgin Islands", + "VI": "U.S. Virgin Islands", + "VN": "Vietnam", + "VU": "Vanuatu", + "WF": "Wallis and Futuna", + "WK": "Wake Island", + "WS": "Samoa", + "YD": "People's Democratic Republic of Yemen", + "YE": "Yemen", + "YT": "Mayotte", + "ZA": "South Africa", + "ZM": "Zambia", + "ZW": "Zimbabwe", + "ZZ": "Unknown or Invalid Region" +} \ No newline at end of file diff --git a/production/js/autocomplete/jquery.autocomplete.js b/production/js/autocomplete/jquery.autocomplete.js index 6988bf58..8f31b4c8 100755 --- a/production/js/autocomplete/jquery.autocomplete.js +++ b/production/js/autocomplete/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.24 +* (c) 2015 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 */ +/*jslint browser: true, white: true, plusplus: true, vars: true */ +/*global define, window, document, jQuery, exports, require */ // 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, @@ -71,6 +74,7 @@ onSearchStart: noop, onSearchComplete: noop, onSearchError: noop, + preserveInput: false, containerClass: 'autocomplete-suggestions', tabDisabled: false, dataType: 'text', @@ -83,7 +87,11 @@ paramName: 'query', transformResult: function (response) { return typeof response === 'string' ? $.parseJSON(response) : response; - } + }, + showNoSuggestionNotice: false, + noSuggestionNotice: 'No results', + orientation: 'bottom', + forceFixPosition: false }; // Shared variables: @@ -99,6 +107,7 @@ that.onChange = null; that.isLocal = false; that.suggestionsContainer = null; + that.noSuggestionsContainer = null; that.options = $.extend({}, defaults, options); that.classes = { selected: 'autocomplete-selected', @@ -119,8 +128,14 @@ Autocomplete.formatResult = function (suggestion, currentValue) { var pattern = '(' + utils.escapeRegExChars(currentValue) + ')'; - - return suggestion.value.replace(new RegExp(pattern, 'gi'), '$1<\/strong>'); + + return suggestion.value + .replace(new RegExp(pattern, 'gi'), '$1<\/strong>') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/<(\/?strong)>/g, '<$1>'); }; Autocomplete.prototype = { @@ -144,6 +159,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); @@ -171,8 +190,6 @@ that.select($(this).data('index')); }); - that.fixPosition(); - that.fixPositionCapture = function () { if (that.visible) { that.fixPosition(); @@ -186,12 +203,13 @@ that.el.on('blur.autocomplete', function () { that.onBlur(); }); that.el.on('focus.autocomplete', function () { that.onFocus(); }); that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); }); + that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); }); }, onFocus: function () { var that = this; that.fixPosition(); - if (that.options.minChars <= that.el.val().length) { + if (that.options.minChars === 0 && that.el.val().length === 0) { that.onValueChange(); } }, @@ -199,6 +217,14 @@ onBlur: function () { this.enableKillerFn(); }, + + abortAjax: function () { + var that = this; + if (that.currentRequest) { + that.currentRequest.abort(); + that.currentRequest = null; + } + }, setOptions: function (suppliedOptions) { var that = this, @@ -212,6 +238,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', @@ -220,6 +248,7 @@ }); }, + clearCache: function () { this.cachedResponse = {}; this.badQueries = []; @@ -234,9 +263,8 @@ disable: function () { var that = this; that.disabled = true; - if (that.currentRequest) { - that.currentRequest.abort(); - } + clearInterval(that.onChangeInterval); + that.abortAjax(); }, enable: function () { @@ -244,27 +272,64 @@ }, 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; } - offset = that.el.offset(); + // 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 }; - styles = { - top: (offset.top + that.el.outerHeight()) + 'px', - left: offset.left + 'px' - }; + 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'; + } + + if (orientation === 'top') { + styles.top += -containerHeight; + } 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(); + } + + 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 () { @@ -281,7 +346,11 @@ var that = this; that.stopKillSuggestions(); that.intervalId = window.setInterval(function () { - that.hide(); + if (that.visible) { + that.el.val(that.currentValue); + that.hide(); + } + that.stopKillSuggestions(); }, 50); }, @@ -336,16 +405,21 @@ that.selectHint(); return; } - // Fall through to RETURN + if (that.selectedIndex === -1) { + that.hide(); + return; + } + that.select(that.selectedIndex); + if (that.options.tabDisabled === false) { + return; + } + break; case keys.RETURN: if (that.selectedIndex === -1) { that.hide(); return; } that.select(that.selectedIndex); - if (e.which === keys.TAB && that.options.tabDisabled === false) { - return; - } break; case keys.UP: that.moveUp(); @@ -394,10 +468,9 @@ var that = this, options = that.options, value = that.el.val(), - query = that.getQuery(value), - index; + query = that.getQuery(value); - if (that.selection) { + if (that.selection && that.currentValue !== query) { that.selection = null; (options.onInvalidateSelection || $.noop).call(that.element); } @@ -407,12 +480,9 @@ that.selectedIndex = -1; // Check existing suggestion for the match before proceeding: - if (options.triggerSelectOnValidInput) { - index = that.findSuggestionIndex(query); - if (index !== -1) { - that.select(index); - return; - } + if (options.triggerSelectOnValidInput && that.isExactMatch(query)) { + that.select(0); + return; } if (query.length < options.minChars) { @@ -422,19 +492,10 @@ } }, - findSuggestionIndex: function (query) { - var that = this, - index = -1, - queryLowerCase = query.toLowerCase(); + isExactMatch: function (query) { + var suggestions = this.suggestions; - $.each(that.suggestions, function (i, suggestion) { - if (suggestion.value.toLowerCase() === queryLowerCase) { - index = i; - return false; - } - }); - - return index; + return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase()); }, getQuery: function (value) { @@ -475,11 +536,25 @@ options = that.options, serviceUrl = options.serviceUrl, params, - cacheKey; + cacheKey, + ajaxSettings; options.params[options.paramName] = q; params = options.ignoreParams ? null : options.params; + if (options.onSearchStart.call(that.element, options.params) === false) { + return; + } + + if ($.isFunction(options.lookup)){ + options.lookup(q, function (data) { + that.suggestions = data.suggestions; + that.suggest(); + options.onSearchComplete.call(that.element, q, data.suggestions); + }); + return; + } + if (that.isLocal) { response = that.getSuggestionsLocal(q); } else { @@ -493,27 +568,30 @@ if (response && $.isArray(response.suggestions)) { that.suggestions = response.suggestions; that.suggest(); + options.onSearchComplete.call(that.element, q, response.suggestions); } else if (!that.isBadQuery(q)) { - if (options.onSearchStart.call(that.element, options.params) === false) { - return; - } - if (that.currentRequest) { - that.currentRequest.abort(); - } - that.currentRequest = $.ajax({ + that.abortAjax(); + + 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); + result = options.transformResult(data, q); 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); }); + } else { + options.onSearchComplete.call(that.element, q, []); } }, @@ -535,44 +613,116 @@ }, hide: function () { - var that = this; + var that = this, + container = $(that.suggestionsContainer); + + if ($.isFunction(that.options.onHide) && that.visible) { + that.options.onHide.call(that.element, container); + } + that.visible = false; that.selectedIndex = -1; + clearInterval(that.onChangeInterval); $(that.suggestionsContainer).hide(); that.signalHint(null); }, suggest: function () { if (this.suggestions.length === 0) { - this.hide(); + if (this.options.showNoSuggestionNotice) { + this.noSuggestions(); + } else { + this.hide(); + } return; } var that = this, options = that.options, + groupBy = options.groupBy, formatResult = options.formatResult, value = that.getQuery(that.currentValue), className = that.classes.suggestion, classSelected = that.classes.selected, container = $(that.suggestionsContainer), + noSuggestionsContainer = $(that.noSuggestionsContainer), beforeRender = options.beforeRender, html = '', - index, - width; + category, + formatGroup = function (suggestion, index) { + var currentCategory = suggestion.data[groupBy]; - if (options.triggerSelectOnValidInput) { - index = that.findSuggestionIndex(value); - if (index !== -1) { - that.select(index); - return; - } + if (category === currentCategory){ + return ''; + } + + category = currentCategory; + + return '
' + category + '
'; + }; + + if (options.triggerSelectOnValidInput && that.isExactMatch(value)) { + that.select(0); + return; } // Build suggestions inner HTML: $.each(that.suggestions, function (i, suggestion) { + if (groupBy){ + html += formatGroup(suggestion, value, i); + } + html += '
' + formatResult(suggestion, value) + '
'; }); + this.adjustContainerWidth(); + + noSuggestionsContainer.detach(); + container.html(html); + + if ($.isFunction(beforeRender)) { + beforeRender.call(that.element, container); + } + + that.fixPosition(); + container.show(); + + // Select first value by default: + if (options.autoSelectFirst) { + that.selectedIndex = 0; + container.scrollTop(0); + container.children('.' + className).first().addClass(classSelected); + } + + 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. @@ -581,23 +731,6 @@ width = that.el.outerWidth() - 2; container.width(width > 0 ? width : 300); } - - container.html(html); - - // Select first value by default: - if (options.autoSelectFirst) { - that.selectedIndex = 0; - container.children().first().addClass(classSelected); - } - - if ($.isFunction(beforeRender)) { - beforeRender.call(that.element, container); - } - - container.show(); - that.visible = true; - - that.findBestHint(); }, findBestHint: function () { @@ -644,6 +777,16 @@ return suggestions; }, + 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; @@ -672,9 +815,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; @@ -730,16 +873,17 @@ adjustScroll: function (index) { var that = this, - activeItem = that.activate(index), - offsetTop, - upperBound, - lowerBound, - heightDelta = 25; + activeItem = that.activate(index); if (!activeItem) { return; } + var offsetTop, + upperBound, + lowerBound, + heightDelta = $(activeItem).outerHeight(); + offsetTop = activeItem.offsetTop; upperBound = $(that.suggestionsContainer).scrollTop(); lowerBound = upperBound + that.options.maxHeight - heightDelta; @@ -750,7 +894,9 @@ $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta); } - that.el.val(that.getValue(that.suggestions[index].value)); + if (!that.options.preserveInput) { + that.el.val(that.getValue(that.suggestions[index].value)); + } that.signalHint(null); }, @@ -761,7 +907,7 @@ that.currentValue = that.getValue(suggestion.value); - if (that.currentValue !== that.el.val()) { + if (that.currentValue !== that.el.val() && !that.options.preserveInput) { that.el.val(that.currentValue); } @@ -804,7 +950,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: