From c81628a66eb9b219ee4d46f8337066e02006b95c Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 23 Aug 2021 21:26:23 +0530 Subject: [PATCH] added async search capability --- src/util/AsyncSearch.js | 124 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/util/AsyncSearch.js diff --git a/src/util/AsyncSearch.js b/src/util/AsyncSearch.js new file mode 100644 index 0000000..1751719 --- /dev/null +++ b/src/util/AsyncSearch.js @@ -0,0 +1,124 @@ +import EventEmitter from 'events'; + +class AsyncSearch extends EventEmitter { + constructor() { + super(); + + this._reset(); + + this.RESULT_SENT = 'RESULT_SENT'; + } + + _reset() { + this.dataList = null; + this.term = null; + this.searchKeys = null; + this.isContain = false; + this.isCaseSensitive = false; + this.ignoreWhitespace = true; + this.limit = null; + this.findingList = []; + + this.searchUptoIndex = 0; + this.sessionStartTimestamp = 0; + } + + _softReset() { + this.term = null; + this.findingList = []; + this.searchUptoIndex = 0; + this.sessionStartTimestamp = 0; + } + + /** + * Setup the search. + * opts.keys are required when dataList items are object. + * + * @param {[string | object]} dataList - A list to search in + * @param {object} opts - Options + * @param {string | [string]} [opts.keys=null] + * @param {boolean} [opts.isContain=false] - Add finding to result if it contain search term + * @param {boolean} [opts.isCaseSensitive=false] + * @param {boolean} [opts.ignoreWhitespace=true] + * @param {number} [opts.limit=null] - Stop search after limit + */ + setup(dataList, opts) { + this._reset(); + this.dataList = dataList; + this.searchKeys = opts?.keys || null; + this.isContain = opts?.isContain || false; + this.isCaseSensitive = opts?.isCaseSensitive || false; + this.ignoreWhitespace = opts?.ignoreWhitespace || true; + this.limit = opts?.limit || null; + } + + search(term) { + this._softReset(); + + this.term = (this.isCaseSensitive) ? term : term.toLocaleLowerCase(); + if (this.ignoreWhitespace) this.term = this.term.replace(' ', ''); + + this._find(this.sessionStartTimestamp, 0); + } + + _find(sessionTimestamp, lastFindingCount) { + if (sessionTimestamp !== this.sessionStartTimestamp) return; + this.sessionStartTimestamp = window.performance.now(); + + for ( + let searchIndex = this.searchUptoIndex; + searchIndex < this.dataList.length; + searchIndex += 1 + ) { + if (this._match(this.dataList[searchIndex])) { + this.findingList.push(this.dataList[searchIndex]); + if (typeof this.limit === 'number' && this.findingList.length >= this.limit) break; + } + + const calcFinishTime = window.performance.now(); + if (calcFinishTime - this.sessionStartTimestamp > 8) { + const thisFindingCount = this.findingList.length; + const thisSessionTimestamp = this.sessionStartTimestamp; + if (lastFindingCount !== thisFindingCount) this._sendFindings(); + + this.searchUptoIndex = searchIndex + 1; + queueMicrotask(() => this._find(thisSessionTimestamp, thisFindingCount)); + return; + } + } + + if (lastFindingCount !== this.findingList.length + || lastFindingCount === 0) this._sendFindings(); + this._softReset(); + } + + _match(item) { + if (typeof item === 'string') { + return this._compare(item); + } + if (typeof item === 'object') { + if (Array.isArray(this.searchKeys)) { + return !!this.searchKeys.find((key) => this._compare(item[key])); + } + if (typeof this.searchKeys === 'string') { + return this._compare(item[this.searchKeys]); + } + } + return false; + } + + _compare(item) { + if (typeof item !== 'string') return false; + let myItem = (this.isCaseSensitive) ? item : item.toLocaleLowerCase(); + if (this.ignoreWhitespace) myItem = myItem.replace(' ', ''); + + if (this.isContain) return myItem.indexOf(this.term) !== -1; + return myItem.startsWith(this.term); + } + + _sendFindings() { + this.emit(this.RESULT_SENT, this.findingList); + } +} + +export default AsyncSearch;