/** * @module nyc/AutoComplete */ import $ from 'jquery' /** * @desc A class to provide fitering functionality for autocomplete * @public * @class */ class AutoComplete { /** * @desc Find matching li elements from inUl and move them to outUl * @public * @method * @param {jQuery|Element|string} inUl The ul element to search * @param {jQuery|Element|string} outUl The ul element to receive results * @param {string} typed The text for searching */ filter(inUl, outUl, typed) { const long = typed.length > 3 const veryLong = typed.length > 6 const filtered = {exact: [], possible: []} const matchers = this.regexp(typed) const all = [] const test = this.test $.merge($(outUl).find('li'), $(inUl).find('li')).each((_, li) => { all.push($(li)) test(matchers, $(li), filtered, long, this.log) }) $(inUl).append(all) if (filtered.exact.length) { $(outUl).prepend(filtered.exact) } else if (!veryLong) { $(outUl).prepend(filtered.possible) } } /** * @private * @method * @param {Object<string,RegExp>} matchers Matchers * @param {jQuery|string|number} item Item * @param {Object<string,Array<JQuery|string|number>>} filtered Filtered * @param {boolean} long If true use exact test */ test(matchers, item, filtered, long) { const text = item.html() if (long) { if (matchers.exact.test(text)) { filtered.exact.push(item) } } if (matchers.possible.test(text)) { filtered.possible.push(item) } } /** * @private * @method * @param {string} typed Typed string * @return {Object<string,RegExp>} Exact and possible regexes */ regexp(typed) { const possibleMatch = new String(typed.replace(/[^a-zA-Z0-9]/g, '')) const exactMatch = new String(typed.replace(/[^a-zA-Z0-9 ]/g, '')) let possible = '^' for (let i = 0; i < possibleMatch.length; i++) { possible += `(?=.*${possibleMatch.charAt(i)})|` } possible = possible.substr(0, possible.length - 1) possible += '.*$' return { exact: new RegExp(`(${exactMatch})`, 'i'), possible: new RegExp(possible, 'i') } } } export default AutoComplete