blob: 2357192f407ac4e0cb1fdbc0bc8c949c5829c293 [file] [log] [blame]
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.autoComplete = factory());
}(this, (function () { 'use strict';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var dataAttribute = "data-id";
var select = {
resultsList: "autoComplete_list",
result: "autoComplete_result",
highlight: "autoComplete_highlighted",
selectedResult: "autoComplete_selected"
};
var keys = {
ENTER: 13,
ARROW_UP: 38,
ARROW_DOWN: 40
};
var getInput = function getInput(selector) {
return typeof selector === "string" ? document.querySelector(selector) : selector();
};
var createResultsList = function createResultsList(renderResults) {
var resultsList = document.createElement(renderResults.element);
resultsList.setAttribute("id", select.resultsList);
if (renderResults.container) {
renderResults.container(resultsList);
}
renderResults.destination.insertAdjacentElement(renderResults.position, resultsList);
return resultsList;
};
var highlight = function highlight(value) {
return "<span class=".concat(select.highlight, ">").concat(value, "</span>");
};
var addResultsToList = function addResultsToList(resultsList, dataSrc, resultItem) {
var fragment = document.createDocumentFragment();
dataSrc.forEach(function (event, record) {
var result = document.createElement(resultItem.element);
var resultIndex = dataSrc[record].index;
result.setAttribute(dataAttribute, resultIndex);
result.setAttribute("class", select.result);
resultItem.content ? resultItem.content(event, result) : result.innerHTML = event.match || event;
fragment.appendChild(result);
});
resultsList.appendChild(fragment);
};
var clearResults = function clearResults(resultsList) {
return resultsList.innerHTML = "";
};
var onSelection = function onSelection(event, field, resultsList, feedback, resultsValues, selection) {
feedback({
event: event,
query: field instanceof HTMLInputElement ? field.value : field.innerHTML,
matches: resultsValues.matches,
results: resultsValues.list.map(function (record) {
return record.value;
}),
selection: resultsValues.list.find(function (value) {
if (event.keyCode === keys.ENTER) {
return value.index === Number(selection.getAttribute(dataAttribute));
} else if (event.type === "mousedown") {
return value.index === Number(event.currentTarget.getAttribute(dataAttribute));
}
})
});
clearResults(resultsList);
};
var navigation = function navigation(input, resultsList, feedback, resultsValues) {
var li = resultsList.childNodes,
liLength = li.length - 1;
var liSelected = undefined,
next;
var removeSelection = function removeSelection(direction) {
liSelected.classList.remove(select.selectedResult);
if (direction === 1) {
next = liSelected.nextSibling;
} else {
next = liSelected.previousSibling;
}
};
var highlightSelection = function highlightSelection(current) {
liSelected = current;
liSelected.classList.add(select.selectedResult);
};
input.onkeydown = function (event) {
if (li.length > 0) {
switch (event.keyCode) {
case keys.ARROW_UP:
if (liSelected) {
removeSelection(0);
if (next) {
highlightSelection(next);
} else {
highlightSelection(li[liLength]);
}
} else {
highlightSelection(li[liLength]);
}
break;
case keys.ARROW_DOWN:
if (liSelected) {
removeSelection(1);
if (next) {
highlightSelection(next);
} else {
highlightSelection(li[0]);
}
} else {
highlightSelection(li[0]);
}
break;
case keys.ENTER:
if (liSelected) {
onSelection(event, input, resultsList, feedback, resultsValues, liSelected);
}
}
}
};
li.forEach(function (selection) {
selection.onmousedown = function (event) {
return onSelection(event, input, resultsList, feedback, resultsValues);
};
});
};
var autoCompleteView = {
getInput: getInput,
createResultsList: createResultsList,
highlight: highlight,
addResultsToList: addResultsToList,
navigation: navigation,
clearResults: clearResults
};
var CustomEventPolyfill = function CustomEventPolyfill(event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
CustomEventPolyfill.prototype = window.Event.prototype;
var CustomEventWrapper = typeof window.CustomEvent === "function" && window.CustomEvent || CustomEventPolyfill;
var initElementClosestPolyfill = function initElementClosestPolyfill() {
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
}
if (!Element.prototype.closest) {
Element.prototype.closest = function (s) {
var el = this;
do {
if (el.matches(s)) {
return el;
}
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
};
var Polyfill = {
CustomEventWrapper: CustomEventWrapper,
initElementClosestPolyfill: initElementClosestPolyfill
};
var autoComplete =
function () {
function autoComplete(config) {
_classCallCheck(this, autoComplete);
var _config$selector = config.selector,
selector = _config$selector === void 0 ? "#autoComplete" : _config$selector,
_config$data = config.data,
key = _config$data.key,
_src = _config$data.src,
_config$data$cache = _config$data.cache,
cache = _config$data$cache === void 0 ? true : _config$data$cache,
query = config.query,
_config$trigger = config.trigger;
_config$trigger = _config$trigger === void 0 ? {} : _config$trigger;
var _config$trigger$event = _config$trigger.event,
event = _config$trigger$event === void 0 ? ["input"] : _config$trigger$event,
_config$trigger$condi = _config$trigger.condition,
condition = _config$trigger$condi === void 0 ? false : _config$trigger$condi,
_config$searchEngine = config.searchEngine,
searchEngine = _config$searchEngine === void 0 ? "strict" : _config$searchEngine,
_config$threshold = config.threshold,
threshold = _config$threshold === void 0 ? 0 : _config$threshold,
_config$debounce = config.debounce,
debounce = _config$debounce === void 0 ? 0 : _config$debounce,
_config$resultsList = config.resultsList;
_config$resultsList = _config$resultsList === void 0 ? {} : _config$resultsList;
var _config$resultsList$r = _config$resultsList.render,
render = _config$resultsList$r === void 0 ? false : _config$resultsList$r,
_config$resultsList$c = _config$resultsList.container,
container = _config$resultsList$c === void 0 ? false : _config$resultsList$c,
destination = _config$resultsList.destination,
_config$resultsList$p = _config$resultsList.position,
position = _config$resultsList$p === void 0 ? "afterend" : _config$resultsList$p,
_config$resultsList$e = _config$resultsList.element,
resultsListElement = _config$resultsList$e === void 0 ? "ul" : _config$resultsList$e,
_config$resultsList$n = _config$resultsList.navigation,
navigation = _config$resultsList$n === void 0 ? false : _config$resultsList$n,
_config$sort = config.sort,
sort = _config$sort === void 0 ? false : _config$sort,
placeHolder = config.placeHolder,
_config$maxResults = config.maxResults,
maxResults = _config$maxResults === void 0 ? 5 : _config$maxResults,
_config$resultItem = config.resultItem;
_config$resultItem = _config$resultItem === void 0 ? {} : _config$resultItem;
var _config$resultItem$co = _config$resultItem.content,
content = _config$resultItem$co === void 0 ? false : _config$resultItem$co,
_config$resultItem$el = _config$resultItem.element,
resultItemElement = _config$resultItem$el === void 0 ? "li" : _config$resultItem$el,
noResults = config.noResults,
_config$highlight = config.highlight,
highlight = _config$highlight === void 0 ? false : _config$highlight,
onSelection = config.onSelection;
var resultsListView = render ? autoCompleteView.createResultsList({
container: container,
destination: destination || autoCompleteView.getInput(selector),
position: position,
element: resultsListElement
}) : null;
this.selector = selector;
this.data = {
src: function src() {
return typeof _src === "function" ? _src() : _src;
},
key: key,
cache: cache
};
this.query = query;
this.trigger = {
event: event,
condition: condition
};
this.searchEngine = searchEngine === "loose" ? "loose" : typeof searchEngine === "function" ? searchEngine : "strict";
this.threshold = threshold;
this.debounce = debounce;
this.resultsList = {
render: render,
view: resultsListView,
navigation: navigation
};
this.sort = sort;
this.placeHolder = placeHolder;
this.maxResults = maxResults;
this.resultItem = {
content: content,
element: resultItemElement
};
this.noResults = noResults;
this.highlight = highlight;
this.onSelection = onSelection;
this.init();
}
_createClass(autoComplete, [{
key: "search",
value: function search(query, record) {
var recordLowerCase = record.toLowerCase();
if (this.searchEngine === "loose") {
query = query.replace(/ /g, "");
var match = [];
var searchPosition = 0;
for (var number = 0; number < recordLowerCase.length; number++) {
var recordChar = record[number];
if (searchPosition < query.length && recordLowerCase[number] === query[searchPosition]) {
recordChar = this.highlight ? autoCompleteView.highlight(recordChar) : recordChar;
searchPosition++;
}
match.push(recordChar);
}
if (searchPosition !== query.length) {
return false;
}
return match.join("");
} else {
if (recordLowerCase.includes(query)) {
var pattern = new RegExp("".concat(query), "i");
query = pattern.exec(record);
return this.highlight ? record.replace(query, autoCompleteView.highlight(query)) : record;
}
}
}
}, {
key: "listMatchedResults",
value: function listMatchedResults(data) {
var _this = this;
return new Promise(function (resolve) {
var resList = [];
data.filter(function (record, index) {
var search = function search(key) {
var recordValue = key ? record[key] : record;
if (recordValue) {
var match = typeof _this.searchEngine === "function" ? _this.searchEngine(_this.queryValue, recordValue) : _this.search(_this.queryValue, recordValue);
if (match && key) {
resList.push({
key: key,
index: index,
match: match,
value: record
});
} else if (match && !key) {
resList.push({
index: index,
match: match,
value: record
});
}
}
};
if (_this.data.key) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = _this.data.key[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var key = _step.value;
search(key);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
_iterator["return"]();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
} else {
search();
}
});
var list = _this.sort ? resList.sort(_this.sort).slice(0, _this.maxResults) : resList.slice(0, _this.maxResults);
return resolve({
matches: resList.length,
list: list
});
});
}
}, {
key: "ignite",
value: function ignite() {
var _this2 = this;
var input = autoCompleteView.getInput(this.selector);
if (this.placeHolder) {
input.setAttribute("placeholder", this.placeHolder);
}
var debounce = function debounce(func, delay) {
var inDebounce;
return function () {
var context = this;
var args = arguments;
clearTimeout(inDebounce);
inDebounce = setTimeout(function () {
return func.apply(context, args);
}, delay);
};
};
var exec = function exec(event) {
var inputValue = input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement ? input.value.toLowerCase() : input.innerHTML.toLowerCase();
var queryValue = _this2.queryValue = _this2.query && _this2.query.manipulate ? _this2.query.manipulate(inputValue) : inputValue;
var renderResultsList = _this2.resultsList.render;
var triggerCondition = _this2.trigger.condition ? _this2.trigger.condition(queryValue) : queryValue.length > _this2.threshold && queryValue.replace(/ /g, "").length;
var eventEmitter = function eventEmitter(event, results) {
input.dispatchEvent(new Polyfill.CustomEventWrapper("autoComplete", {
bubbles: true,
detail: {
event: event,
input: inputValue,
query: queryValue,
matches: results ? results.matches : null,
results: results ? results.list : null
},
cancelable: true
}));
};
if (renderResultsList) {
var resultsList = _this2.resultsList.view;
var clearResults = autoCompleteView.clearResults(resultsList);
if (triggerCondition) {
_this2.listMatchedResults(_this2.dataStream, event).then(function (list) {
eventEmitter(event, list);
if (_this2.resultsList.render) {
if (list.list.length === 0 && _this2.noResults) {
_this2.noResults();
} else {
autoCompleteView.addResultsToList(resultsList, list.list, _this2.resultItem);
if (_this2.onSelection) {
_this2.resultsList.navigation ? _this2.resultsList.navigation(event, input, resultsList, _this2.onSelection, list) : autoCompleteView.navigation(input, resultsList, _this2.onSelection, list);
}
}
}
});
} else {
eventEmitter(event);
}
} else if (!renderResultsList && triggerCondition) {
_this2.listMatchedResults(_this2.dataStream, event).then(function (list) {
eventEmitter(event, list);
});
}
};
var run = function run(event) {
Promise.resolve(_this2.data.cache ? _this2.dataStream : _this2.data.src()).then(function (data) {
_this2.dataStream = data;
exec(event);
});
};
this.trigger.event.forEach(function (eventType) {
input.addEventListener(eventType, debounce(function (event) {
return run(event);
}, _this2.debounce));
});
}
}, {
key: "init",
value: function init() {
var _this3 = this;
if (this.data.cache) {
Promise.resolve(this.data.src()).then(function (data) {
_this3.dataStream = data;
_this3.ignite();
});
} else {
this.ignite();
}
Polyfill.initElementClosestPolyfill();
}
}]);
return autoComplete;
}();
return autoComplete;
})));