diff --git a/awesomplete.js b/awesomplete.js index fe4d0c38..5116dd54 100644 --- a/awesomplete.js +++ b/awesomplete.js @@ -217,19 +217,23 @@ _.prototype = { if (value.length >= this.minChars && this._list.length > 0) { this.index = -1; - // Populate list with options that match - this.ul.innerHTML = ""; - - this._list - .filter(function(item) { - return me.filter(item, value); - }) - .sort(this.sort) - .every(function(text, i) { - me.ul.appendChild(me.item(text, value)); - - return i < me.maxItems - 1; - }); + // Populate list with options that match + this.ul.innerHTML = ""; + + var filteredAndSortedList = + this._list + .filter(function(item) { + return me.filter(item, value); + }); + + $.mergeSort(filteredAndSortedList ,this.sort); + + filteredAndSortedList + .every(function(text, i) { + me.ul.appendChild(me.item(text, value)); + + return i < me.maxItems - 1; + }); if (this.ul.children.length === 0) { this.close(); @@ -354,6 +358,50 @@ $.regExpEscape = function (s) { return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); } +$.mergeSort = function(array, compareFn) { + + msort(array, 0, array.length); + + function msort(array, begin, end) { + var size = end - begin; + if (size < 2) return; + + var begin_right = begin + Math.floor(size / 2); + + msort(array, begin, begin_right); + msort(array, begin_right, end); + merge(array, begin, begin_right, end); + } + + function merge(array, begin, begin_right, end) { + for (; begin < begin_right; ++begin) { + if (compareFn(array[begin], array[begin_right]) > 0) { + var v = array[begin]; + array[begin] = array[begin_right]; + insert(array, begin_right, end, v); + } + } + } + + function insert(array, begin, end, v) { + while (begin + 1 < end && compareFn(array[begin + 1], v) < 0) { + swap(array, begin, begin + 1); + ++begin; + } + array[begin] = v; + } + + function swap(array, a, b) { + var tmp = array[a]; + array[a] = array[b]; + array[b] = tmp; + } + +} + + + + // Initialization function init() { diff --git a/awesomplete.min.js b/awesomplete.min.js index f5a6f19a..406eab92 100644 --- a/awesomplete.min.js +++ b/awesomplete.min.js @@ -1,10 +1,11 @@ // Awesomplete - Lea Verou - MIT license -(function(){function m(a,b){for(var c in a){var g=a[c],e=this.input.getAttribute("data-"+c.toLowerCase());this[c]="number"===typeof g?parseInt(e):!1===g?null!==e:g instanceof Function?null:e;this[c]||0===this[c]||(this[c]=c in b?b[c]:g)}}function d(a,b){return"string"===typeof a?(b||document).querySelector(a):a||null}function h(a,b){return k.call((b||document).querySelectorAll(a))}function l(){h("input.awesomplete").forEach(function(a){new f(a)})}var f=function(a,b){var c=this;this.input=d(a);this.input.setAttribute("autocomplete", -"off");this.input.setAttribute("aria-autocomplete","list");b=b||{};m.call(this,{minChars:2,maxItems:10,autoFirst:!1,filter:f.FILTER_CONTAINS,sort:f.SORT_BYLENGTH,item:function(a,b){return d.create("li",{innerHTML:a.replace(RegExp(d.regExpEscape(b.trim()),"gi"),"$&"),"aria-selected":"false"})},replace:function(a){this.input.value=a}},b);this.index=-1;this.container=d.create("div",{className:"awesomplete",around:a});this.ul=d.create("ul",{hidden:"",inside:this.container});this.status=d.create("span", +(function(){function q(a,b){for(var c in a){var e=a[c],g=this.input.getAttribute("data-"+c.toLowerCase());this[c]="number"===typeof e?parseInt(g):!1===e?null!==g:e instanceof Function?null:g;this[c]||0===this[c]||(this[c]=c in b?b[c]:e)}}function d(a,b){return"string"===typeof a?(b||document).querySelector(a):a||null}function l(a,b){return n.call((b||document).querySelectorAll(a))}function m(){l("input.awesomplete").forEach(function(a){new f(a)})}var f=function(a,b){var c=this;this.input=d(a);this.input.setAttribute("autocomplete", +"off");this.input.setAttribute("aria-autocomplete","list");b=b||{};q.call(this,{minChars:2,maxItems:10,autoFirst:!1,filter:f.FILTER_CONTAINS,sort:f.SORT_BYLENGTH,item:function(a,b){return d.create("li",{innerHTML:a.replace(RegExp(d.regExpEscape(b.trim()),"gi"),"$&"),"aria-selected":"false"})},replace:function(a){this.input.value=a}},b);this.index=-1;this.container=d.create("div",{className:"awesomplete",around:a});this.ul=d.create("ul",{hidden:"",inside:this.container});this.status=d.create("span", {className:"visually-hidden",role:"status","aria-live":"assertive","aria-relevant":"additions",inside:this.container});d.bind(this.input,{input:this.evaluate.bind(this),blur:this.close.bind(this),keydown:function(a){var b=a.keyCode;if(c.opened)if(13===b&&c.selected)a.preventDefault(),c.select();else if(27===b)c.close();else if(38===b||40===b)a.preventDefault(),c[38===b?"previous":"next"]()}});d.bind(this.input.form,{submit:this.close.bind(this)});d.bind(this.ul,{mousedown:function(a){a=a.target;if(a!== -this){for(;a&&!/li/i.test(a.nodeName);)a=a.parentNode;a&&c.select(a)}}});this.input.hasAttribute("list")?(this.list="#"+a.getAttribute("list"),a.removeAttribute("list")):this.list=this.input.getAttribute("data-list")||b.list||[];f.all.push(this)};f.prototype={set list(a){Array.isArray(a)?this._list=a:"string"===typeof a&&-1=this.minChars&&0=this.minChars&&0h))for(h=d+Math.floor(h/2),c(a,d,h),c(a,h,f);db(r[k+1],l);){var p=r,m=k+1,q=p[k];p[k]=p[m];p[m]=q;++k}r[k]=l}}c(a,0,a.length)};"undefined"!==typeof Document&& +("loading"!==document.readyState?m():document.addEventListener("DOMContentLoaded",m));f.$=d;f.$$=l;"undefined"!==typeof self&&(self.Awesomplete=f);"object"===typeof exports&&(module.exports=f);return f})(); diff --git a/test/awesompleteShared.js b/test/awesompleteShared.js index 64d53f43..b7b485b4 100644 --- a/test/awesompleteShared.js +++ b/test/awesompleteShared.js @@ -41,5 +41,12 @@ var shared = { expect(li.getAttribute('aria-selected')).toBe('true'); expect(shared.awesompleter.status.textContent).toBe(li.textContent); } + }, + getListFromResults: function (awesompleter){ + return [].slice.call(awesompleter.ul.children) + .map(function(value){ + return value.textContent; + }); } + }; diff --git a/test/awesompleteSpec.js b/test/awesompleteSpec.js index 601bf9d8..31998673 100644 --- a/test/awesompleteSpec.js +++ b/test/awesompleteSpec.js @@ -60,6 +60,7 @@ describe("awesomplete",function(){ shared.awesompleter = awesompleter; }) + it("should have a non-empty list", shared.expectListLengthToBe(12)); it("should have 3 min chars", shared.expectMinCharsToBe(3)); @@ -79,7 +80,21 @@ describe("awesomplete",function(){ it("should autocomplete when a suggestion is selected", shared.expectSelectingFirstSuggestionToWorkWith('test')); it("should autocomplete the first suggestion when autoFirst is true", shared.expectAutoFirstToWorkWith('test')); - }) + + // see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.11 + // Chrome's native Array.prototype.sort does not perform stable sorts with arrays above 10 items. + it("should perform a stable sort inside evaluation chain",function(){ + var origList = ["New York","Staten Island","Jamaica","Schenectady","Flushing","White Plains","Great Neck","Yonkers","Utica","Elmira","Binghamton"]; + + awesompleter = new Awesomplete(dummyInput,{minChars:0, maxItems:20, sort: function(a,b){ return 0;}}); + awesompleter.list = origList; + awesompleter.evaluate(); + + expect(shared.getListFromResults(awesompleter)).toEqual(origList); + }); + + + }); }); describe("awesomplete helpers",function(){