diff --git a/static/skin/autoComplete.js b/static/skin/autoComplete.js new file mode 100644 index 000000000..ec025c50a --- /dev/null +++ b/static/skin/autoComplete.js @@ -0,0 +1,654 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.autoComplete = factory()); +}(this, (function () { 'use strict'; + + function ownKeys(object, enumerableOnly) { + var keys = Object.keys(object); + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(object); + + if (enumerableOnly) { + symbols = symbols.filter(function (sym) { + return Object.getOwnPropertyDescriptor(object, sym).enumerable; + }); + } + + keys.push.apply(keys, symbols); + } + + return keys; + } + + function _objectSpread2(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + + if (i % 2) { + ownKeys(Object(source), true).forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + + return target; + } + + function _typeof(obj) { + "@babel/helpers - typeof"; + + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); + } + + function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + } + + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); + } + + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) return _arrayLikeToArray(arr); + } + + function _iterableToArray(iter) { + if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); + } + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + + return arr2; + } + + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + function _createForOfIteratorHelper(o, allowArrayLike) { + var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; + + if (!it) { + if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { + if (it) o = it; + var i = 0; + + var F = function () {}; + + return { + s: F, + n: function () { + if (i >= o.length) return { + done: true + }; + return { + done: false, + value: o[i++] + }; + }, + e: function (e) { + throw e; + }, + f: F + }; + } + + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + var normalCompletion = true, + didErr = false, + err; + return { + s: function () { + it = it.call(o); + }, + n: function () { + var step = it.next(); + normalCompletion = step.done; + return step; + }, + e: function (e) { + didErr = true; + err = e; + }, + f: function () { + try { + if (!normalCompletion && it.return != null) it.return(); + } finally { + if (didErr) throw err; + } + } + }; + } + + var select$1 = function select(element) { + return typeof element === "string" ? document.querySelector(element) : element(); + }; + var create = function create(tag, options) { + var el = typeof tag === "string" ? document.createElement(tag) : tag; + for (var key in options) { + var val = options[key]; + if (key === "inside") { + val.append(el); + } else if (key === "dest") { + select$1(val[0]).insertAdjacentElement(val[1], el); + } else if (key === "around") { + var ref = val; + ref.parentNode.insertBefore(el, ref); + el.append(ref); + if (ref.getAttribute("autofocus") != null) ref.focus(); + } else if (key in el) { + el[key] = val; + } else { + el.setAttribute(key, val); + } + } + return el; + }; + var getQuery = function getQuery(field) { + return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement ? field.value : field.innerHTML; + }; + var format = function format(value, diacritics) { + value = value.toString().toLowerCase(); + return diacritics ? value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").normalize("NFC") : value; + }; + var debounce = function debounce(callback, duration) { + var timer; + return function () { + clearTimeout(timer); + timer = setTimeout(function () { + return callback(); + }, duration); + }; + }; + var checkTrigger = function checkTrigger(query, condition, threshold) { + return condition ? condition(query) : query.length >= threshold; + }; + var mark = function mark(value, cls) { + return create("mark", _objectSpread2({ + innerHTML: value + }, typeof cls === "string" && { + "class": cls + })).outerHTML; + }; + + var configure = (function (ctx) { + var name = ctx.name, + options = ctx.options, + resultsList = ctx.resultsList, + resultItem = ctx.resultItem; + for (var option in options) { + if (_typeof(options[option]) === "object") { + if (!ctx[option]) ctx[option] = {}; + for (var subOption in options[option]) { + ctx[option][subOption] = options[option][subOption]; + } + } else { + ctx[option] = options[option]; + } + } + ctx.selector = ctx.selector || "#" + name; + resultsList.destination = resultsList.destination || ctx.selector; + resultsList.id = resultsList.id || name + "_list_" + ctx.id; + resultItem.id = resultItem.id || name + "_result"; + ctx.input = select$1(ctx.selector); + }); + + var eventEmitter = (function (name, ctx) { + ctx.input.dispatchEvent(new CustomEvent(name, { + bubbles: true, + detail: ctx.feedback, + cancelable: true + })); + }); + + var search = (function (query, record, options) { + var _ref = options || {}, + mode = _ref.mode, + diacritics = _ref.diacritics, + highlight = _ref.highlight; + var nRecord = format(record, diacritics); + record = record.toString(); + query = format(query, diacritics); + if (mode === "loose") { + query = query.replace(/ /g, ""); + var qLength = query.length; + var cursor = 0; + var match = Array.from(record).map(function (character, index) { + if (cursor < qLength && nRecord[index] === query[cursor]) { + character = highlight ? mark(character, highlight) : character; + cursor++; + } + return character; + }).join(""); + if (cursor === qLength) return match; + } else { + var _match = nRecord.indexOf(query); + if (~_match) { + query = record.substring(_match, _match + query.length); + _match = highlight ? record.replace(query, mark(query, highlight)) : record; + return _match; + } + } + }); + + var getData = function getData(ctx, query) { + return new Promise(function ($return, $error) { + var data; + data = ctx.data; + if (data.cache && data.store) return $return(); + return new Promise(function ($return, $error) { + if (typeof data.src === "function") { + return data.src(query).then($return, $error); + } + return $return(data.src); + }).then(function ($await_4) { + try { + ctx.feedback = data.store = $await_4; + eventEmitter("response", ctx); + return $return(); + } catch ($boundEx) { + return $error($boundEx); + } + }, $error); + }); + }; + var findMatches = function findMatches(query, ctx) { + var data = ctx.data, + searchEngine = ctx.searchEngine; + var matches = []; + data.store.forEach(function (value, index) { + var find = function find(key) { + var record = key ? value[key] : value; + var match = typeof searchEngine === "function" ? searchEngine(query, record) : search(query, record, { + mode: searchEngine, + diacritics: ctx.diacritics, + highlight: ctx.resultItem.highlight + }); + if (!match) return; + var result = { + match: match, + value: value + }; + if (key) result.key = key; + matches.push(result); + }; + if (data.keys) { + var _iterator = _createForOfIteratorHelper(data.keys), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var key = _step.value; + find(key); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + } else { + find(); + } + }); + if (data.filter) matches = data.filter(matches); + var results = matches.slice(0, ctx.resultsList.maxResults); + ctx.feedback = { + query: query, + matches: matches, + results: results + }; + eventEmitter("results", ctx); + }; + + var Expand = "aria-expanded"; + var Active = "aria-activedescendant"; + var Selected = "aria-selected"; + var feedback = function feedback(ctx, index) { + ctx.feedback.selection = _objectSpread2({ + index: index + }, ctx.feedback.results[index]); + }; + var render = function render(ctx) { + var resultsList = ctx.resultsList, + list = ctx.list, + resultItem = ctx.resultItem, + feedback = ctx.feedback; + var matches = feedback.matches, + results = feedback.results; + ctx.cursor = -1; + list.innerHTML = ""; + if (matches.length || resultsList.noResults) { + var fragment = new DocumentFragment(); + results.forEach(function (result, index) { + var element = create(resultItem.tag, _objectSpread2({ + id: "".concat(resultItem.id, "_").concat(index), + role: "option", + innerHTML: result.match, + inside: fragment + }, resultItem["class"] && { + "class": resultItem["class"] + })); + if (resultItem.element) resultItem.element(element, result); + }); + list.append(fragment); + if (resultsList.element) resultsList.element(list, feedback); + open(ctx); + } else { + close(ctx); + } + }; + var open = function open(ctx) { + if (ctx.isOpen) return; + (ctx.wrapper || ctx.input).setAttribute(Expand, true); + ctx.list.removeAttribute("hidden"); + ctx.isOpen = true; + eventEmitter("open", ctx); + }; + var close = function close(ctx) { + if (!ctx.isOpen) return; + (ctx.wrapper || ctx.input).setAttribute(Expand, false); + ctx.input.setAttribute(Active, ""); + ctx.list.setAttribute("hidden", ""); + ctx.isOpen = false; + eventEmitter("close", ctx); + }; + var goTo = function goTo(index, ctx) { + var resultItem = ctx.resultItem; + var results = ctx.list.getElementsByTagName(resultItem.tag); + var cls = resultItem.selected ? resultItem.selected.split(" ") : false; + if (ctx.isOpen && results.length) { + var _results$index$classL; + var state = ctx.cursor; + if (index >= results.length) index = 0; + if (index < 0) index = results.length - 1; + ctx.cursor = index; + if (state > -1) { + var _results$state$classL; + results[state].removeAttribute(Selected); + if (cls) (_results$state$classL = results[state].classList).remove.apply(_results$state$classL, _toConsumableArray(cls)); + } + results[index].setAttribute(Selected, true); + if (cls) (_results$index$classL = results[index].classList).add.apply(_results$index$classL, _toConsumableArray(cls)); + ctx.input.setAttribute(Active, results[ctx.cursor].id); + ctx.list.scrollTop = results[index].offsetTop - ctx.list.clientHeight + results[index].clientHeight + 5; + ctx.feedback.cursor = ctx.cursor; + feedback(ctx, index); + eventEmitter("navigate", ctx); + } + }; + var next = function next(ctx) { + goTo(ctx.cursor + 1, ctx); + }; + var previous = function previous(ctx) { + goTo(ctx.cursor - 1, ctx); + }; + var select = function select(ctx, event, index) { + index = index >= 0 ? index : ctx.cursor; + if (index < 0) return; + ctx.feedback.event = event; + feedback(ctx, index); + eventEmitter("selection", ctx); + close(ctx); + }; + var click = function click(event, ctx) { + var itemTag = ctx.resultItem.tag.toUpperCase(); + var items = Array.from(ctx.list.querySelectorAll(itemTag)); + var item = event.target.closest(itemTag); + if (item && item.nodeName === itemTag) { + select(ctx, event, items.indexOf(item)); + } + }; + var navigate = function navigate(event, ctx) { + switch (event.keyCode) { + case 40: + case 38: + event.preventDefault(); + event.keyCode === 40 ? next(ctx) : previous(ctx); + break; + case 13: + if (!ctx.submit) event.preventDefault(); + if (ctx.cursor >= 0) select(ctx, event); + break; + case 9: + if (ctx.resultsList.tabSelect && ctx.cursor >= 0) select(ctx, event); + break; + case 27: + ctx.input.value = ""; + close(ctx); + break; + } + }; + + function start (ctx, q) { + var _this = this; + return new Promise(function ($return, $error) { + var queryVal, condition; + queryVal = q || getQuery(ctx.input); + queryVal = ctx.query ? ctx.query(queryVal) : queryVal; + condition = checkTrigger(queryVal, ctx.trigger, ctx.threshold); + if (condition) { + return getData(ctx, queryVal).then(function ($await_2) { + try { + if (ctx.feedback instanceof Error) return $return(); + findMatches(queryVal, ctx); + if (ctx.resultsList) render(ctx); + return $If_1.call(_this); + } catch ($boundEx) { + return $error($boundEx); + } + }, $error); + } else { + close(ctx); + return $If_1.call(_this); + } + function $If_1() { + return $return(); + } + }); + } + + var eventsManager = function eventsManager(events, callback) { + for (var element in events) { + for (var event in events[element]) { + callback(element, event); + } + } + }; + var addEvents = function addEvents(ctx) { + var events = ctx.events; + var run = debounce(function () { + return start(ctx); + }, ctx.debounce); + var publicEvents = ctx.events = _objectSpread2({ + input: _objectSpread2({}, events && events.input) + }, ctx.resultsList && { + list: events ? _objectSpread2({}, events.list) : {} + }); + var privateEvents = { + input: { + input: function input() { + run(); + }, + keydown: function keydown(event) { + navigate(event, ctx); + }, + blur: function blur() { + close(ctx); + } + }, + list: { + mousedown: function mousedown(event) { + event.preventDefault(); + }, + click: function click$1(event) { + click(event, ctx); + } + } + }; + eventsManager(privateEvents, function (element, event) { + if (!ctx.resultsList && event !== "input") return; + if (publicEvents[element][event]) return; + publicEvents[element][event] = privateEvents[element][event]; + }); + eventsManager(publicEvents, function (element, event) { + ctx[element].addEventListener(event, publicEvents[element][event]); + }); + }; + var removeEvents = function removeEvents(ctx) { + eventsManager(ctx.events, function (element, event) { + ctx[element].removeEventListener(event, ctx.events[element][event]); + }); + }; + + function init (ctx) { + var _this = this; + return new Promise(function ($return, $error) { + var placeHolder, resultsList, parentAttrs; + placeHolder = ctx.placeHolder; + resultsList = ctx.resultsList; + parentAttrs = { + role: "combobox", + "aria-owns": resultsList.id, + "aria-haspopup": true, + "aria-expanded": false + }; + create(ctx.input, _objectSpread2(_objectSpread2({ + "aria-controls": resultsList.id, + "aria-autocomplete": "both" + }, placeHolder && { + placeholder: placeHolder + }), !ctx.wrapper && _objectSpread2({}, parentAttrs))); + if (ctx.wrapper) ctx.wrapper = create("div", _objectSpread2({ + around: ctx.input, + "class": ctx.name + "_wrapper" + }, parentAttrs)); + if (resultsList) ctx.list = create(resultsList.tag, _objectSpread2({ + dest: [resultsList.destination, resultsList.position], + id: resultsList.id, + role: "listbox", + hidden: "hidden" + }, resultsList["class"] && { + "class": resultsList["class"] + })); + addEvents(ctx); + if (ctx.data.cache) { + return getData(ctx).then(function ($await_2) { + try { + return $If_1.call(_this); + } catch ($boundEx) { + return $error($boundEx); + } + }, $error); + } + function $If_1() { + eventEmitter("init", ctx); + return $return(); + } + return $If_1.call(_this); + }); + } + + function extend (autoComplete) { + var prototype = autoComplete.prototype; + prototype.init = function () { + init(this); + }; + prototype.start = function (query) { + start(this, query); + }; + prototype.unInit = function () { + if (this.wrapper) { + var parentNode = this.wrapper.parentNode; + parentNode.insertBefore(this.input, this.wrapper); + parentNode.removeChild(this.wrapper); + } + removeEvents(this); + }; + prototype.open = function () { + open(this); + }; + prototype.close = function () { + close(this); + }; + prototype.goTo = function (index) { + goTo(index, this); + }; + prototype.next = function () { + next(this); + }; + prototype.previous = function () { + previous(this); + }; + prototype.select = function (index) { + select(this, null, index); + }; + prototype.search = function (query, record, options) { + return search(query, record, options); + }; + } + + function autoComplete(config) { + this.options = config; + this.id = autoComplete.instances = (autoComplete.instances || 0) + 1; + this.name = "autoComplete"; + this.wrapper = 1; + this.threshold = 1; + this.debounce = 0; + this.resultsList = { + position: "afterend", + tag: "ul", + maxResults: 5 + }; + this.resultItem = { + tag: "li" + }; + configure(this); + extend.call(this, autoComplete); + init(this); + } + + return autoComplete; + +}))); diff --git a/static/skin/css/autoComplete.css b/static/skin/css/autoComplete.css index 19267485c..01f2cd01c 100644 --- a/static/skin/css/autoComplete.css +++ b/static/skin/css/autoComplete.css @@ -1,3 +1,4 @@ +/* Modified from https://github.com/TarekRaafat/autoComplete.js (version 1.2.6)*/ .autoComplete_wrapper { display: inline-block; position: relative;