From bdd1715b249561ed919e4f03a06aec1f4c327335 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Fri, 6 Mar 2015 21:29:56 +0100 Subject: Use awesomplete as autocomplete lib and remove jQuery - shaarli/Shaarli#148 * Add awesomplete dependancy (source + min + CSS) * Remove jQuery and jQuery-UI dependancy * Few CSS ajustements * Use tags complete list as RainTPL var (and display it as HTML) * Remove "disable jQuery" feature * Remove tag list web service --- inc/awesomplete.js | 388 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 inc/awesomplete.js (limited to 'inc/awesomplete.js') diff --git a/inc/awesomplete.js b/inc/awesomplete.js new file mode 100644 index 00000000..fae550e2 --- /dev/null +++ b/inc/awesomplete.js @@ -0,0 +1,388 @@ +/** + * Simple, lightweight, usable local autocomplete library for modern browsers + * Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P + * @author Lea Verou http://leaverou.github.io/awesomplete + * MIT license + */ + +(function () { + + var _ = function (input, o) { + var me = this; + + // Setup + + this.input = $(input); + this.input.setAttribute("aria-autocomplete", "list"); + + o = o || {}; + + configure.call(this, { + minChars: 2, + maxItems: 10, + autoFirst: false, + 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; + } + }, o); + + this.index = -1; + + // Create necessary elements + + this.container = $.create("div", { + className: "awesomplete", + around: input + }); + + this.ul = $.create("ul", { + hidden: "", + inside: this.container + }); + + this.status = $.create("span", { + className: "visually-hidden", + role: "status", + "aria-live": "assertive", + "aria-relevant": "additions", + inside: this.container + }); + + // Bind events + + $.bind(this.input, { + "input": this.evaluate.bind(this), + "blur": this.close.bind(this), + "keydown": function(evt) { + var c = evt.keyCode; + + // If the dropdown `ul` is in view, then act on keydown for the following keys: + // Enter / Esc / Up / Down + if(me.opened) { + if (c === 13 && me.selected) { // Enter + evt.preventDefault(); + me.select(); + } + else if (c === 27) { // Esc + me.close(); + } + else if (c === 38 || c === 40) { // Down/Up arrow + evt.preventDefault(); + me[c === 38? "previous" : "next"](); + } + } + } + }); + + $.bind(this.input.form, {"submit": this.close.bind(this)}); + + $.bind(this.ul, {"mousedown": function(evt) { + var li = evt.target; + + if (li !== this) { + + while (li && !/li/i.test(li.nodeName)) { + li = li.parentNode; + } + + if (li) { + me.select(li); + } + } + }}); + + if (this.input.hasAttribute("list")) { + this.list = "#" + input.getAttribute("list"); + input.removeAttribute("list"); + } + else { + this.list = this.input.getAttribute("data-list") || o.list || []; + } + + _.all.push(this); + }; + + _.prototype = { + set list(list) { + if (Array.isArray(list)) { + this._list = list; + } + else if (typeof list === "string" && list.indexOf(",") > -1) { + this._list = list.split(/\s*,\s*/); + } + else { // Element or CSS selector + list = $(list); + + if (list && list.children) { + this._list = slice.apply(list.children).map(function (el) { + return el.innerHTML.trim(); + }); + } + } + + if (document.activeElement === this.input) { + this.evaluate(); + } + }, + + get selected() { + return this.index > -1; + }, + + get opened() { + return this.ul && this.ul.getAttribute("hidden") == null; + }, + + close: function () { + this.ul.setAttribute("hidden", ""); + this.index = -1; + + $.fire(this.input, "awesomplete-close"); + }, + + open: function () { + this.ul.removeAttribute("hidden"); + + if (this.autoFirst && this.index === -1) { + this.goto(0); + } + + $.fire(this.input, "awesomplete-open"); + }, + + next: function () { + var count = this.ul.children.length; + + this.goto(this.index < count - 1? this.index + 1 : -1); + }, + + previous: function () { + var count = this.ul.children.length; + + this.goto(this.selected? this.index - 1 : count - 1); + }, + + // Should not be used, highlights specific item without any checks! + goto: function (i) { + var lis = this.ul.children; + + if (this.selected) { + lis[this.index].setAttribute("aria-selected", "false"); + } + + this.index = i; + + if (i > -1 && lis.length > 0) { + lis[i].setAttribute("aria-selected", "true"); + this.status.textContent = lis[i].textContent; + } + + $.fire(this.input, "awesomplete-highlight"); + }, + + select: function (selected) { + selected = selected || this.ul.children[this.index]; + + if (selected) { + var prevented; + + $.fire(this.input, "awesomplete-select", { + text: selected.textContent, + preventDefault: function () { + prevented = true; + } + }); + + if (!prevented) { + this.replace(selected.textContent); + this.close(); + $.fire(this.input, "awesomplete-selectcomplete"); + } + } + }, + + evaluate: function() { + var me = this; + var value = this.input.value; + + 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; + }); + + if (this.ul.children.length === 0) { + this.close(); + } else { + this.open(); + } + } + else { + this.close(); + } + } + }; + +// Static methods/properties + + _.all = []; + + _.FILTER_CONTAINS = function (text, input) { + return RegExp($.regExpEscape(input.trim()), "i").test(text); + }; + + _.FILTER_STARTSWITH = function (text, input) { + return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text); + }; + + _.SORT_BYLENGTH = function (a, b) { + if (a.length !== b.length) { + return a.length - b.length; + } + + return a < b? -1 : 1; + }; + +// Private functions + + function configure(properties, o) { + for (var i in properties) { + var initial = properties[i], + attrValue = this.input.getAttribute("data-" + i.toLowerCase()); + + if (typeof initial === "number") { + this[i] = +attrValue; + } + else if (initial === false) { // Boolean options must be false by default anyway + this[i] = attrValue !== null; + } + else if (initial instanceof Function) { + this[i] = null; + } + else { + this[i] = attrValue; + } + + this[i] = this[i] || o[i] || initial; + } + } + +// Helpers + + var slice = Array.prototype.slice; + + function $(expr, con) { + return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; + } + + function $$(expr, con) { + return slice.call((con || document).querySelectorAll(expr)); + } + + $.create = function(tag, o) { + var element = document.createElement(tag); + + for (var i in o) { + var val = o[i]; + + if (i === "inside") { + $(val).appendChild(element); + } + else if (i === "around") { + var ref = $(val); + ref.parentNode.insertBefore(element, ref); + element.appendChild(ref); + } + else if (i in element) { + element[i] = val; + } + else { + element.setAttribute(i, val); + } + } + + return element; + }; + + $.bind = function(element, o) { + if (element) { + for (var event in o) { + var callback = o[event]; + + event.split(/\s+/).forEach(function (event) { + element.addEventListener(event, callback); + }); + } + } + }; + + $.fire = function(target, type, properties) { + var evt = document.createEvent("HTMLEvents"); + + evt.initEvent(type, true, true ); + + for (var j in properties) { + evt[j] = properties[j]; + } + + target.dispatchEvent(evt); + }; + + $.regExpEscape = function (s) { + return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); + } + +// Initialization + + function init() { + $$("input.awesomplete").forEach(function (input) { + new Awesomplete(input); + }); + } + +// Are we in a browser? Check for Document constructor + if (typeof Document !== 'undefined') { + // DOM already loaded? + if (document.readyState !== "loading") { + init(); + } + else { + // Wait for it + document.addEventListener("DOMContentLoaded", init); + } + } + + _.$ = $; + _.$$ = $$; + +// Make sure to export Awesomplete on self when in a browser + if (typeof self !== 'undefined') { + self.Awesomplete = _; + } + +// Expose Awesomplete as a CJS module + if (typeof exports === 'object') { + module.exports = _; + } + + return _; + +}()); -- cgit v1.2.3