X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=inc%2Fawesomplete.js;h=32f49e5b4c703a178f22ed4a67f6be43731328fe;hb=03809dbb4dd232bc4269add7b46776e703bc3b81;hp=fae550e250f3e5c00a04bc0b28e50827ed4bce05;hpb=9a631bab7fb7ca0c60c9fd1c688b3c58ecec40f8;p=github%2Fshaarli%2FShaarli.git diff --git a/inc/awesomplete.js b/inc/awesomplete.js index fae550e2..32f49e5b 100644 --- a/inc/awesomplete.js +++ b/inc/awesomplete.js @@ -12,26 +12,23 @@ // Setup + this.isOpened = false; + this.input = $(input); + this.input.setAttribute("autocomplete", "off"); this.input.setAttribute("aria-autocomplete", "list"); o = o || {}; - configure.call(this, { + configure(this, { minChars: 2, maxItems: 10, autoFirst: false, + data: _.DATA, filter: _.FILTER_CONTAINS, sort: _.SORT_BYLENGTH, - item: function (text, input) { - return $.create("li", { - innerHTML: text.replace(RegExp($.regExpEscape(input.trim()), "gi"), "$&"), - "aria-selected": "false" - }); - }, - replace: function (text) { - this.input.value = text; - } + item: _.ITEM, + replace: _.REPLACE }, o); this.index = -1; @@ -44,7 +41,7 @@ }); this.ul = $.create("ul", { - hidden: "", + hidden: "hidden", inside: this.container }); @@ -60,7 +57,7 @@ $.bind(this.input, { "input": this.evaluate.bind(this), - "blur": this.close.bind(this), + "blur": this.close.bind(this, { reason: "blur" }), "keydown": function(evt) { var c = evt.keyCode; @@ -72,7 +69,7 @@ me.select(); } else if (c === 27) { // Esc - me.close(); + me.close({ reason: "esc" }); } else if (c === 38 || c === 40) { // Down/Up arrow evt.preventDefault(); @@ -82,7 +79,7 @@ } }); - $.bind(this.input.form, {"submit": this.close.bind(this)}); + $.bind(this.input.form, {"submit": this.close.bind(this, { reason: "submit" })}); $.bind(this.ul, {"mousedown": function(evt) { var li = evt.target; @@ -93,15 +90,16 @@ li = li.parentNode; } - if (li) { - me.select(li); + if (li && evt.button === 0) { // Only select on left click + evt.preventDefault(); + me.select(li, evt.target); } } }}); if (this.input.hasAttribute("list")) { - this.list = "#" + input.getAttribute("list"); - input.removeAttribute("list"); + this.list = "#" + this.input.getAttribute("list"); + this.input.removeAttribute("list"); } else { this.list = this.input.getAttribute("data-list") || o.list || []; @@ -122,9 +120,18 @@ list = $(list); if (list && list.children) { - this._list = slice.apply(list.children).map(function (el) { - return el.innerHTML.trim(); + var items = []; + slice.apply(list.children).forEach(function (el) { + if (!el.disabled) { + var text = el.textContent.trim(); + var value = el.value || text; + var label = el.label || text; + if (value !== "") { + items.push({ label: label, value: value }); + } + } }); + this._list = items; } } @@ -138,18 +145,24 @@ }, get opened() { - return this.ul && this.ul.getAttribute("hidden") == null; + return this.isOpened; }, - close: function () { + close: function (o) { + if (!this.opened) { + return; + } + this.ul.setAttribute("hidden", ""); + this.isOpened = false; this.index = -1; - $.fire(this.input, "awesomplete-close"); + $.fire(this.input, "awesomplete-close", o || {}); }, open: function () { this.ul.removeAttribute("hidden"); + this.isOpened = true; if (this.autoFirst && this.index === -1) { this.goto(0); @@ -160,14 +173,14 @@ next: function () { var count = this.ul.children.length; - - this.goto(this.index < count - 1? this.index + 1 : -1); + this.goto(this.index < count - 1 ? this.index + 1 : (count ? 0 : -1) ); }, previous: function () { var count = this.ul.children.length; + var pos = this.index - 1; - this.goto(this.selected? this.index - 1 : count - 1); + this.goto(this.selected && pos !== -1 ? pos : count - 1); }, // Should not be used, highlights specific item without any checks! @@ -183,28 +196,37 @@ if (i > -1 && lis.length > 0) { lis[i].setAttribute("aria-selected", "true"); this.status.textContent = lis[i].textContent; - } - $.fire(this.input, "awesomplete-highlight"); + // scroll to highlighted element in case parent's height is fixed + this.ul.scrollTop = lis[i].offsetTop - this.ul.clientHeight + lis[i].clientHeight; + + $.fire(this.input, "awesomplete-highlight", { + text: this.suggestions[this.index] + }); + } }, - select: function (selected) { - selected = selected || this.ul.children[this.index]; + select: function (selected, origin) { + if (selected) { + this.index = $.siblingIndex(selected); + } else { + selected = this.ul.children[this.index]; + } if (selected) { - var prevented; + var suggestion = this.suggestions[this.index]; - $.fire(this.input, "awesomplete-select", { - text: selected.textContent, - preventDefault: function () { - prevented = true; - } + var allowed = $.fire(this.input, "awesomplete-select", { + text: suggestion, + origin: origin || selected }); - if (!prevented) { - this.replace(selected.textContent); - this.close(); - $.fire(this.input, "awesomplete-selectcomplete"); + if (allowed) { + this.replace(suggestion); + this.close({ reason: "select" }); + $.fire(this.input, "awesomplete-selectcomplete", { + text: suggestion + }); } } }, @@ -218,25 +240,28 @@ // Populate list with options that match this.ul.innerHTML = ""; - this._list + this.suggestions = this._list + .map(function(item) { + return new Suggestion(me.data(item, value)); + }) .filter(function(item) { return me.filter(item, value); }) .sort(this.sort) - .every(function(text, i) { - me.ul.appendChild(me.item(text, value)); + .slice(0, this.maxItems); - return i < me.maxItems - 1; - }); + this.suggestions.forEach(function(text) { + me.ul.appendChild(me.item(text, value)); + }); if (this.ul.children.length === 0) { - this.close(); + this.close({ reason: "nomatches" }); } else { this.open(); } } else { - this.close(); + this.close({ reason: "nomatches" }); } } }; @@ -261,27 +286,58 @@ return a < b? -1 : 1; }; + _.ITEM = function (text, input) { + var html = input.trim() === '' ? text : text.replace(RegExp($.regExpEscape(input.trim()), "gi"), "$&"); + return $.create("li", { + innerHTML: html, + "aria-selected": "false" + }); + }; + + _.REPLACE = function (text) { + this.input.value = text.value; + }; + + _.DATA = function (item/*, input*/) { return item; }; + // Private functions - function configure(properties, o) { + function Suggestion(data) { + var o = Array.isArray(data) + ? { label: data[0], value: data[1] } + : typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data }; + + this.label = o.label || o.value; + this.value = o.value; + } + Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", { + get: function() { return this.label.length; } + }); + Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () { + return "" + this.label; + }; + + function configure(instance, properties, o) { for (var i in properties) { var initial = properties[i], - attrValue = this.input.getAttribute("data-" + i.toLowerCase()); + attrValue = instance.input.getAttribute("data-" + i.toLowerCase()); if (typeof initial === "number") { - this[i] = +attrValue; + instance[i] = parseInt(attrValue); } else if (initial === false) { // Boolean options must be false by default anyway - this[i] = attrValue !== null; + instance[i] = attrValue !== null; } else if (initial instanceof Function) { - this[i] = null; + instance[i] = null; } else { - this[i] = attrValue; + instance[i] = attrValue; } - this[i] = this[i] || o[i] || initial; + if (!instance[i] && instance[i] !== 0) { + instance[i] = (i in o)? o[i] : initial; + } } } @@ -343,23 +399,29 @@ evt[j] = properties[j]; } - target.dispatchEvent(evt); + return target.dispatchEvent(evt); }; $.regExpEscape = function (s) { return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); - } + }; + + $.siblingIndex = function (el) { + /* eslint-disable no-cond-assign */ + for (var i = 0; el = el.previousElementSibling; i++); + return i; + }; // Initialization function init() { $$("input.awesomplete").forEach(function (input) { - new Awesomplete(input); + new _(input); }); } // Are we in a browser? Check for Document constructor - if (typeof Document !== 'undefined') { + if (typeof Document !== "undefined") { // DOM already loaded? if (document.readyState !== "loading") { init(); @@ -374,15 +436,15 @@ _.$$ = $$; // Make sure to export Awesomplete on self when in a browser - if (typeof self !== 'undefined') { + if (typeof self !== "undefined") { self.Awesomplete = _; } // Expose Awesomplete as a CJS module - if (typeof exports === 'object') { + if (typeof module === "object" && module.exports) { module.exports = _; } return _; -}()); +}()); \ No newline at end of file