From 513cdda31667f0058b24e8f66d87ddfcf89b7fb4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 09:21:38 -0600 Subject: [PATCH] Refactor filtered search manager --- .../filtered_search/dropdown_hint.js.es6 | 2 +- .../filtered_search/dropdown_non_user.js.es6 | 2 +- .../filtered_search/dropdown_user.js.es6 | 2 +- .../filtered_search_dropdown.js.es6 | 2 +- .../filtered_search_dropdown_manager.js.es6 | 174 ++++++++++++++++++ .../filtered_search_manager.js.es6 | 160 +--------------- 6 files changed, 185 insertions(+), 157 deletions(-) create mode 100644 app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index d445a796f43..53952e6bc63 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -42,7 +42,7 @@ const tag = selected.querySelector('.js-filter-tag').innerText.trim(); if (tag.length) { - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); + gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(token)); } this.dismissDropdown(); this.dispatchInputEvent(); diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index f03c27c3ec0..e4df39cfde1 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -25,7 +25,7 @@ if (!dataValueSet) { const title = e.detail.selected.querySelector('.js-data-value').innerText.trim(); const name = `${this.symbol}${this.getEscapedText(title)}`; - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(name)); + gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(name)); } this.dismissDropdown(!dataValueSet); diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 6827ab1658a..d3c3be9b914 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -27,7 +27,7 @@ if (!dataValueSet) { const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); + gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(username)); } this.dismissDropdown(!dataValueSet); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index c63ba1acf0b..38ecbbf552d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -57,7 +57,7 @@ const dataValue = selected.getAttribute('data-value'); if (dataValue) { - gl.FilteredSearchManager.addWordToInput(dataValue); + gl.FilteredSearchDropdownManager.addWordToInput(dataValue); } return dataValue !== null; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 new file mode 100644 index 00000000000..67a474985c0 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -0,0 +1,174 @@ +/* eslint-disable no-param-reassign */ +((global) => { + class FilteredSearchDropdownManager { + constructor() { + this.tokenizer = gl.FilteredSearchTokenizer; + this.filteredSearchInput = document.querySelector('.filtered-search'); + + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('page:fetch', this.cleanupWrapper); + } + + cleanup() { + if (this.droplab) { + this.droplab.destroy(); + this.droplab = null; + } + + this.setupMapping(); + + document.removeEventListener('page:fetch', this.cleanupWrapper); + } + + setupMapping() { + this.mapping = { + author: { + reference: null, + gl: 'DropdownUser', + element: document.querySelector('#js-dropdown-author'), + }, + assignee: { + reference: null, + gl: 'DropdownUser', + element: document.querySelector('#js-dropdown-assignee'), + }, + milestone: { + reference: null, + gl: 'DropdownNonUser', + extraArguments: ['milestones.json', '%'], + element: document.querySelector('#js-dropdown-milestone'), + }, + label: { + reference: null, + gl: 'DropdownNonUser', + extraArguments: ['labels.json', '~'], + element: document.querySelector('#js-dropdown-label'), + }, + hint: { + reference: null, + gl: 'DropdownHint', + element: document.querySelector('#js-dropdown-hint'), + }, + } + } + + static addWordToInput(word, addSpace) { + const filteredSearchInput = document.querySelector('.filtered-search') + const filteredSearchValue = filteredSearchInput.value; + const hasExistingValue = filteredSearchValue.length !== 0; + const { lastToken } = gl.FilteredSearchTokenizer.processTokens(filteredSearchValue); + + if (lastToken.hasOwnProperty('key')) { + console.log(lastToken); + // Spaces inside the token means that the token value will be escaped by quotes + const hasQuotes = lastToken.value.indexOf(' ') !== -1; + const lengthToRemove = hasQuotes ? lastToken.value.length + 2 : lastToken.value.length; + filteredSearchInput.value = filteredSearchValue.slice(0, -1 * (lengthToRemove)); + } + + filteredSearchInput.value += hasExistingValue && addSpace ? ` ${word}` : word; + } + + updateCurrentDropdownOffset() { + this.updateDropdownOffset(this.currentDropdown); + } + + updateDropdownOffset(key) { + const filterIconPadding = 27; + const offset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; + + this.mapping[key].reference.setOffset(offset); + } + + load(key, firstLoad = false) { + console.log(`🦄 load ${key} dropdown`); + const glClass = this.mapping[key].gl; + const element = this.mapping[key].element; + let forceShowList = false; + + if (!this.mapping[key].reference) { + var dl = this.droplab; + const defaultArguments = [null, dl, element, this.filteredSearchInput]; + const glArguments = defaultArguments.concat(this.mapping[key].extraArguments || []); + + this.mapping[key].reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); + } + + if (firstLoad) { + this.mapping[key].reference.configure(); + } + + if (this.currentDropdown === 'hint') { + // Clicked from hint dropdown + forceShowList = true; + } + + this.updateDropdownOffset(key); + this.mapping[key].reference.render(firstLoad, forceShowList); + + this.currentDropdown = key; + } + + loadDropdown(dropdownName = '') { + let firstLoad = false; + + if(!this.droplab) { + firstLoad = true; + this.droplab = new DropLab(); + } + + if (!this.font) { + this.font = window.getComputedStyle(this.filteredSearchInput).font; + } + + const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName.toLowerCase())[0]; + const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping.hasOwnProperty(match.key); + const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; + + if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { + const key = match && match.hasOwnProperty('key') ? match.key : 'hint'; + this.load(key, firstLoad); + } + + gl.droplab = this.droplab; + } + + setDropdown() { + const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); + + if (typeof lastToken === 'string') { + // Token is not fully initialized yet + // because it has no value + // Eg. token = 'label:' + const { tokenKey } = this.tokenizer.parseToken(lastToken); + this.loadDropdown(tokenKey); + } else if (lastToken.hasOwnProperty('key')) { + // Token has been initialized into an object + // because it has a value + this.loadDropdown(lastToken.key); + } else { + this.loadDropdown('hint'); + } + } + + resetDropdowns() { + // Force current dropdown to hide + this.mapping[this.currentDropdown].reference.hideDropdown(); + + // Re-Load dropdown + this.setDropdown(); + + // Reset filters for current dropdown + this.mapping[this.currentDropdown].reference.resetFilters(); + + // Reposition dropdown so that it is aligned with cursor + this.updateDropdownOffset(this.currentDropdown); + } + + destroyDroplab() { + this.droplab.destroy(); + } + } + + global.FilteredSearchDropdownManager = FilteredSearchDropdownManager; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c92d669114e..d9ea44b3a13 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -75,159 +75,24 @@ this.tokenizer = gl.FilteredSearchTokenizer; this.filteredSearchInput = document.querySelector('.filtered-search'); this.clearSearchButton = document.querySelector('.clear-search'); + this.dropdownManager = new gl.FilteredSearchDropdownManager(); - this.setupMapping(); + this.dropdownManager.setupMapping(); this.bindEvents(); loadSearchParamsFromURL(); - this.setDropdown(); + this.dropdownManager.setDropdown(); this.cleanupWrapper = this.cleanup.bind(this); document.addEventListener('page:fetch', this.cleanupWrapper); } cleanup() { - console.log('cleanup') - - if (this.droplab) { - this.droplab.destroy(); - this.droplab = null; - } - - this.setupMapping(); - this.unbindEvents(); document.removeEventListener('page:fetch', this.cleanupWrapper); } - setupMapping() { - this.mapping = { - author: { - reference: null, - gl: 'DropdownUser', - element: document.querySelector('#js-dropdown-author'), - }, - assignee: { - reference: null, - gl: 'DropdownUser', - element: document.querySelector('#js-dropdown-assignee'), - }, - milestone: { - reference: null, - gl: 'DropdownNonUser', - extraArguments: ['milestones.json', '%'], - element: document.querySelector('#js-dropdown-milestone'), - }, - label: { - reference: null, - gl: 'DropdownNonUser', - extraArguments: ['labels.json', '~'], - element: document.querySelector('#js-dropdown-label'), - }, - hint: { - reference: null, - gl: 'DropdownHint', - element: document.querySelector('#js-dropdown-hint'), - }, - } - } - - static addWordToInput(word, addSpace) { - const filteredSearchInput = document.querySelector('.filtered-search') - const filteredSearchValue = filteredSearchInput.value; - const hasExistingValue = filteredSearchValue.length !== 0; - const { lastToken } = gl.FilteredSearchTokenizer.processTokens(filteredSearchValue); - - if (lastToken.hasOwnProperty('key')) { - console.log(lastToken); - // Spaces inside the token means that the token value will be escaped by quotes - const hasQuotes = lastToken.value.indexOf(' ') !== -1; - const lengthToRemove = hasQuotes ? lastToken.value.length + 2 : lastToken.value.length; - filteredSearchInput.value = filteredSearchValue.slice(0, -1 * (lengthToRemove)); - } - - filteredSearchInput.value += hasExistingValue && addSpace ? ` ${word}` : word; - } - - updateDropdownOffset(key) { - const filterIconPadding = 27; - const offset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; - - this.mapping[key].reference.setOffset(offset); - } - - load(key, firstLoad = false) { - console.log(`🦄 load ${key} dropdown`); - const glClass = this.mapping[key].gl; - const element = this.mapping[key].element; - let forceShowList = false; - - if (!this.mapping[key].reference) { - var dl = this.droplab; - const defaultArguments = [null, dl, element, this.filteredSearchInput]; - const glArguments = defaultArguments.concat(this.mapping[key].extraArguments || []); - - this.mapping[key].reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); - } - - if (firstLoad) { - this.mapping[key].reference.configure(); - } - - if (this.currentDropdown === 'hint') { - // Clicked from hint dropdown - forceShowList = true; - } - - this.updateDropdownOffset(key); - this.mapping[key].reference.render(firstLoad, forceShowList); - - this.currentDropdown = key; - } - - loadDropdown(dropdownName = '') { - let firstLoad = false; - - if(!this.droplab) { - firstLoad = true; - this.droplab = new DropLab(); - } - - if (!this.font) { - this.font = window.getComputedStyle(this.filteredSearchInput).font; - } - - const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName.toLowerCase())[0]; - const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping.hasOwnProperty(match.key); - const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; - - if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { - const key = match && match.hasOwnProperty('key') ? match.key : 'hint'; - this.load(key, firstLoad); - } - - gl.droplab = this.droplab; - } - - setDropdown() { - const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); - - if (typeof lastToken === 'string') { - // Token is not fully initialized yet - // because it has no value - // Eg. token = 'label:' - const { tokenKey } = this.tokenizer.parseToken(lastToken); - this.loadDropdown(tokenKey); - } else if (lastToken.hasOwnProperty('key')) { - // Token has been initialized into an object - // because it has a value - this.loadDropdown(lastToken.key); - } else { - this.loadDropdown('hint'); - } - } - bindEvents() { - this.setDropdownWrapper = this.setDropdown.bind(this); + this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); this.checkForEnterWrapper = this.checkForEnter.bind(this); this.clearSearchWrapper = this.clearSearch.bind(this); this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); @@ -254,24 +119,13 @@ this.filteredSearchInput.value = ''; this.clearSearchButton.classList.add('hidden'); - - // Force current dropdown to hide - this.mapping[this.currentDropdown].reference.hideDropdown(); - - // Re-Load dropdown - this.setDropdown(); - - // Reset filters for current dropdown - this.mapping[this.currentDropdown].reference.resetFilters(); - - // Reposition dropdown so that it is aligned with cursor - this.updateDropdownOffset(this.currentDropdown); + this.dropdownManager.resetDropdowns(); } checkForBackspace(e) { if (e.keyCode === 8) { // Reposition dropdown so that it is aligned with cursor - this.updateDropdownOffset(this.currentDropdown); + this.dropdownManager.updateCurrentDropdownOffset(); } } @@ -282,7 +136,7 @@ e.preventDefault(); // Prevent droplab from opening dropdown - this.droplab.destroy(); + this.dropdownManager.destroyDroplab(); this.search(); }