Commit ed4e525a authored by Clement Ho's avatar Clement Ho

Code cleanup

parent c5029c65
......@@ -39,6 +39,10 @@ var DropDown = function(list, trigger) {
this.getItems();
this.addEvents();
this.initialState = list.innerHTML;
if (this.initialState.indexOf('{{') == -1) {
debugger
}
};
Object.assign(DropDown.prototype, {
......@@ -138,6 +142,10 @@ Object.assign(DropDown.prototype, {
this.list.style.display = 'none';
this.hidden = true;
},
destroy: function() {
this.hide();
}
});
module.exports = DropDown;
......@@ -247,7 +255,7 @@ require('./window')(function(w){
changeHookList: function(trigger, list, plugins, config) {
trigger = document.querySelector('[data-id="'+trigger+'"]');
list = document.querySelector(list);
// list = document.querySelector(list);
this.hooks.every(function(hook, i) {
if(hook.trigger === trigger) {
// Restore initial State
......@@ -503,6 +511,7 @@ Object.assign(HookInput.prototype, {
this.restoreInitialState();
this.removeEvents();
this.removePlugins();
this.list.destroy();
}
});
......
......@@ -8,14 +8,12 @@ require('../window')(function(w){
this.hook = hook;
this.notLoading();
this.hook.trigger.addEventListener('keydown.dl', this.debounceTrigger.bind(this));
this.debounceTriggerWrapper = this.debounceTrigger.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.debounceTriggerWrapper);
this.hook.trigger.addEventListener('focus', this.debounceTriggerWrapper);
this.trigger();
},
debounceTriggerWrapper() {
return this.debounceTrigger.bind(this.hook);
},
notLoading: function notLoading() {
this.loading = false;
},
......@@ -57,7 +55,8 @@ require('../window')(function(w){
params[config.searchKey] = searchValue;
var self = this;
this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) {
self.hook.list.addData.call(self.hook.list, data[0]);
self.hook.restoreInitialState.call(self.hook);
self.hook.list.setData.call(self.hook.list, data[0]);
self.notLoading();
});
},
......@@ -93,7 +92,7 @@ require('../window')(function(w){
clearTimeout(this.timeout);
}
this.hook.trigger.removeEventListener('keydown.dl', this.debounceTrigger);
this.hook.trigger.removeEventListener('keydown.dl', this.debounceTriggerWrapper);
this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper);
}
};
......
......@@ -6,6 +6,19 @@
constructor(droplab, dropdown, input) {
super(droplab, dropdown, input);
this.listId = 'js-dropdown-assignee';
this.config = {
droplabAjaxFilter: {
endpoint: '/autocomplete/users.json',
searchKey: 'search',
params: {
per_page: 20,
active: true,
project_id: 2,
current_user: true,
},
searchValueFunction: this.getSearchInput,
}
};
}
itemClicked(e) {
......@@ -21,27 +34,25 @@
renderContent() {
// TODO: Pass elements instead of querySelectors
this.droplab.changeHookList(this.hookId, '#js-dropdown-assignee', [droplabAjax], {
droplabAjax: {
endpoint: '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=&current_user=true&push_code_to_protected_branches=&author_id=&skip_users=',
method: 'setData',
}
});
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config);
}
filterMethod(item, query) {
getSearchInput() {
const query = document.querySelector('.filtered-search').value;
const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query);
const valueWithoutColon = value.slice(1).toLowerCase();
const valueWithoutColon = value.slice(1);
const hasPrefix = valueWithoutColon[0] === '@';
const valueWithoutPrefix = valueWithoutColon.slice(1);
const username = item.username.toLowerCase();
const name = item.name.toLowerCase();
const noUsernameMatch = username.indexOf(valueWithoutPrefix) === -1 && username.indexOf(valueWithoutColon) === -1;
const noNameMatch = name.indexOf(valueWithoutColon) === -1;
if (hasPrefix) {
return valueWithoutPrefix;
} else {
return valueWithoutColon;
}
}
item.droplab_hidden = noUsernameMatch && noNameMatch;
return item;
configure() {
this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init();
}
}
......
......@@ -6,18 +6,7 @@
constructor(droplab, dropdown, input) {
super(droplab, dropdown, input);
this.listId = 'js-dropdown-author';
}
itemClicked(e) {
const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim();
gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username));
this.dismissDropdown();
}
renderContent() {
// TODO: Pass elements instead of querySelectors
this.droplab.changeHookList(this.hookId, '#js-dropdown-author', [droplabAjaxFilter], {
this.config = {
droplabAjaxFilter: {
endpoint: '/autocomplete/users.json',
searchKey: 'search',
......@@ -29,7 +18,19 @@
},
searchValueFunction: this.getSearchInput,
}
});
};
}
itemClicked(e) {
const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim();
gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username));
this.dismissDropdown();
}
renderContent() {
// TODO: Pass elements instead of querySelectors
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config);
}
getSearchInput() {
......@@ -45,6 +46,10 @@
return valueWithoutColon;
}
}
configure() {
this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init();
}
}
global.DropdownAuthor = DropdownAuthor;
......
......@@ -24,26 +24,30 @@
constructor(droplab, dropdown, input) {
super(droplab, dropdown, input);
this.listId = 'js-dropdown-hint';
this.config = {
droplabFilter: {
template: 'hint',
filterFunction: this.filterMethod,
}
};
}
itemClicked(e) {
const token = e.detail.selected.querySelector('.js-filter-hint').innerText.trim();
const tag = e.detail.selected.querySelector('.js-filter-tag').innerText.trim();
const selected = e.detail.selected;
if (!selected.hasAttribute('data-value')) {
const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
if (tag.length) {
gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token));
if (tag.length) {
gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token));
}
}
this.dismissDropdown();
}
renderContent() {
this.droplab.changeHookList(this.hookId, '#js-dropdown-hint', [droplabFilter], {
droplabFilter: {
template: 'hint',
filterFunction: this.filterMethod,
}
});
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
......@@ -60,11 +64,7 @@
}
configure() {
this.droplab.addHook(this.input, this.dropdown, [droplabFilter], {
droplabFilter: {
template: 'hint',
}
}).init();
this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init();
}
}
......
......@@ -6,7 +6,15 @@
constructor(droplab, dropdown, input) {
super(droplab, dropdown, input);
this.listId = 'js-dropdown-label';
this.filterSymbol = '~';
this.config = {
droplabAjax: {
endpoint: 'labels.json',
method: 'setData',
},
droplabFilter: {
filterFunction: this.filterWithSymbol.bind(this, '~'),
}
};
}
itemClicked(e) {
......@@ -22,17 +30,11 @@
}
renderContent() {
// TODO: Pass elements instead of querySelectors
// TODO: Don't bind filterWithSymbol to (this), just pass the symbol
this.droplab.changeHookList(this.hookId, '#js-dropdown-label', [droplabAjax, droplabFilter], {
droplabAjax: {
endpoint: 'labels.json',
method: 'setData',
},
droplabFilter: {
filterFunction: this.filterWithSymbol.bind(this),
}
});
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config);
}
configure() {
this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init();
}
}
......
......@@ -6,7 +6,15 @@
constructor(droplab, dropdown, input) {
super(droplab, dropdown, input);
this.listId = 'js-dropdown-milestone';
this.filterSymbol = '%';
this.config = {
droplabAjax: {
endpoint: 'milestones.json',
method: 'setData',
},
droplabFilter: {
filterFunction: this.filterWithSymbol.bind(this, '%'),
}
};
}
itemClicked(e) {
......@@ -22,16 +30,11 @@
}
renderContent() {
// TODO: Pass elements instead of querySelectors
this.droplab.changeHookList(this.hookId, '#js-dropdown-milestone', [droplabAjax, droplabFilter], {
droplabAjax: {
endpoint: 'milestones.json',
method: 'setData',
},
droplabFilter: {
filterFunction: this.filterWithSymbol.bind(this),
}
});
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config);
}
configure() {
this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init();
}
}
......
......@@ -12,11 +12,16 @@
}
bindEvents() {
this.dropdown.addEventListener('click.dl', this.itemClicked.bind(this));
this.itemClickedWrapper = this.itemClicked.bind(this);
this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
}
unbindEvents() {
this.dropdown.removeEventListener('click.dl', this.itemClicked.bind(this));
this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
}
getCurrentHook() {
return this.droplab.hooks.filter(h => h.id === this.hookId)[0];
}
getEscapedText(text) {
......@@ -49,34 +54,8 @@
// Overridden by dropdown sub class
}
getFilterConfig(filterKeyword) {
const config = {};
const filterConfig = {};
if (filterKeyword) {
filterConfig.text = filterKeyword;
}
if (this.filterMethod) {
filterConfig.filter = this.filterMethod;
}
config[this.hookId] = filterConfig;
return config;
}
destroy() {
this.input.setAttribute(DATA_DROPDOWN_TRIGGER, '');
this.droplab.setConfig(this.getFilterConfig());
this.droplab.setData(this.hookId, []);
this.unbindEvents();
}
dismissDropdown() {
this.input.focus();
// Propogate input change to FilteredSearchManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new Event('input'));
renderContent() {
// Overriden by dropdown sub class
}
setAsDropdown() {
......@@ -97,13 +76,12 @@
return dataValue !== null;
}
getCurrentHook() {
return this.droplab.hooks.filter(h => h.id === this.hookId)[0];
}
renderContent() {
// Overriden by dropdown sub class
}
dismissDropdown() {
this.input.focus();
// Propogate input change to FilteredSearchManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new Event('input'));
}
render(forceRenderContent) {
this.setAsDropdown();
......@@ -134,14 +112,7 @@
}
}
hide() {
const currentHook = this.getCurrentHook();
if (currentHook) {
currentHook.list.hide();
}
}
filterWithSymbol(item, query) {
filterWithSymbol(filterSymbol, item, query) {
const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query);
const valueWithoutColon = value.slice(1).toLowerCase();
const prefix = valueWithoutColon[0];
......@@ -149,8 +120,8 @@
const title = item.title.toLowerCase();
// Eg. this.filterSymbol = ~ for labels
const matchWithoutPrefix = prefix === this.filterSymbol && title.indexOf(valueWithoutPrefix) !== -1;
// Eg. filterSymbol = ~ for labels
const matchWithoutPrefix = prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1;
const match = title.indexOf(valueWithoutColon) !== -1;
item.droplab_hidden = !match && !matchWithoutPrefix;
......
......@@ -69,20 +69,19 @@
}
}
let dropdownHint;
let dropdownAuthor;
let dropdownAssignee;
let dropdownMilestone;
let dropdownLabel;
class FilteredSearchManager {
constructor() {
this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchInput = document.querySelector('.filtered-search');
this.clearSearchButton = document.querySelector('.clear-search');
this.setupMapping();
this.bindEvents();
loadSearchParamsFromURL();
this.setDropdown();
document.addEventListener('page:change', this.cleanup);
this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('page:fetch', this.cleanupWrapper);
}
cleanup() {
......@@ -93,124 +92,105 @@
this.droplab = null;
}
dropdownHint = null;
dropdownAuthor = null;
dropdownAssignee = null;
dropdownMilestone = null;
dropdownLabel = null;
this.setupMapping();
document.removeEventListener('page:fetch', this.cleanupWrapper);
}
document.removeEventListener('page:change', this.cleanup);
setupMapping() {
this.mapping = {
author: {
reference: null,
gl: 'DropdownAuthor',
element: document.querySelector('#js-dropdown-author'),
},
assignee: {
reference: null,
gl: 'DropdownAssignee',
element: document.querySelector('#js-dropdown-assignee'),
},
milestone: {
reference: null,
gl: 'DropdownMilestone',
element: document.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownLabel',
element: document.querySelector('#js-dropdown-label'),
},
hint: {
reference: null,
gl: 'DropdownHint',
element: document.querySelector('#js-dropdown-hint'),
},
}
}
static addWordToInput(word, addSpace) {
const filteredSearchValue = document.querySelector('.filtered-search').value;
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;
document.querySelector('.filtered-search').value = filteredSearchValue.slice(0, -1 * (lengthToRemove));
filteredSearchInput.value = filteredSearchValue.slice(0, -1 * (lengthToRemove));
}
document.querySelector('.filtered-search').value += hasExistingValue && addSpace ? ` ${word}` : word;
filteredSearchInput.value += hasExistingValue && addSpace ? ` ${word}` : word;
}
loadDropdown(dropdownName = '', hideDropdown) {
let firstLoad = false;
const filteredSearch = document.querySelector('.filtered-search');
if(!this.droplab) {
firstLoad = true;
this.droplab = new DropLab();
}
dropdownName = dropdownName.toLowerCase();
load(key, firstLoad = false) {
console.log(`🦄 load ${key} dropdown`);
const glClass = this.mapping[key].gl;
const element = this.mapping[key].element;
const filterIconPadding = 27;
const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName)[0];
const dropdownOffset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding;
if (!this.font) {
this.font = window.getComputedStyle(filteredSearch).font;
if (!this.mapping[key].reference) {
this.mapping[key].reference = new gl[glClass](this.droplab, element, this.filteredSearchInput);
}
if (match && this.currentDropdown !== match.key) {
console.log(`🦄 load ${match.key} dropdown`);
const dynamicDropdownPadding = 12;
const dropdownOffset = gl.text.getTextWidth(filteredSearch.value, this.font) + filterIconPadding + dynamicDropdownPadding;
const dropdownAuthorElement = document.querySelector('#js-dropdown-author');
const dropdownAssigneeElement = document.querySelector('#js-dropdown-assignee');
const dropdownMilestoneElement = document.querySelector('#js-dropdown-milestone');
const dropdownLabelElemenet = document.querySelector('#js-dropdown-label');
this.dismissCurrentDropdown();
this.currentDropdown = match.key;
if (match.key === 'author') {
if (!dropdownAuthor) {
dropdownAuthor = new gl.DropdownAuthor(this.droplab, dropdownAuthorElement, filteredSearch);
}
dropdownAuthor.setOffset(dropdownOffset);
dropdownAuthor.render();
} else if (match.key === 'assignee') {
if (!dropdownAssignee) {
dropdownAssignee = new gl.DropdownAssignee(this.droplab, dropdownAssigneeElement, filteredSearch);
}
dropdownAssignee.setOffset(dropdownOffset);
dropdownAssignee.render();
} else if (match.key === 'milestone') {
if (!dropdownMilestone) {
dropdownMilestone = new gl.DropdownMilestone(this.droplab, dropdownMilestoneElement, filteredSearch);
}
if (firstLoad) {
this.mapping[key].reference.configure();
}
dropdownMilestone.setOffset(dropdownOffset);
dropdownMilestone.render();
} else if (match.key === 'label') {
if (!dropdownLabel) {
dropdownLabel = new gl.DropdownLabel(this.droplab, dropdownLabelElemenet, filteredSearch);
}
this.mapping[key].reference.setOffset(dropdownOffset);
this.mapping[key].reference.render(firstLoad);
dropdownLabel.setOffset(dropdownOffset);
dropdownLabel.render();
}
this.currentDropdown = key;
}
} else if (!match && this.currentDropdown !== 'hint') {
console.log('🦄 load hint dropdown');
loadDropdown(dropdownName = '') {
let firstLoad = false;
const dropdownOffset = gl.text.getTextWidth(filteredSearch.value, this.font) + filterIconPadding;
const dropdownHintElement = document.querySelector('#js-dropdown-hint');
if(!this.droplab) {
firstLoad = true;
this.droplab = new DropLab();
}
this.dismissCurrentDropdown();
this.currentDropdown = 'hint';
if (!dropdownHint) {
dropdownHint = new gl.DropdownHint(this.droplab, dropdownHintElement, filteredSearch);
}
if (!this.font) {
this.font = window.getComputedStyle(this.filteredSearchInput).font;
}
if (firstLoad) {
dropdownHint.configure();
}
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';
dropdownHint.setOffset(dropdownOffset);
dropdownHint.render(firstLoad);
if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
const key = match && match.hasOwnProperty('key') ? match.key : 'hint';
this.load(key, firstLoad);
}
}
dismissCurrentDropdown() {
// if (this.currentDropdown === 'hint') {
// dropdownHint.hide();
// } else if (this.currentDropdown === 'author') {
// // dropdownAuthor.hide();
// }
gl.droplab = this.droplab;
}
setDropdown() {
const { lastToken } = this.tokenizer.processTokens(document.querySelector('.filtered-search').value);
const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value);
if (typeof lastToken === 'string') {
// Token is not fully initialized yet
......@@ -228,32 +208,20 @@
}
bindEvents() {
const filteredSearchInput = document.querySelector('.filtered-search');
filteredSearchInput.addEventListener('input', this.setDropdown.bind(this));
filteredSearchInput.addEventListener('input', toggleClearSearchButton);
filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this));
document.querySelector('.clear-search').addEventListener('click', this.clearSearch.bind(this));
this.filteredSearchInput.addEventListener('input', this.setDropdown.bind(this));
this.filteredSearchInput.addEventListener('input', toggleClearSearchButton);
this.filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this));
this.clearSearchButton.addEventListener('click', this.clearSearch.bind(this));
}
clearSearch(e) {
e.stopPropagation();
e.preventDefault();
document.querySelector('.filtered-search').value = '';
document.querySelector('.clear-search').classList.add('hidden');
this.filteredSearchInput.value = '';
this.clearSearchButton.classList.add('hidden');
dropdownHint.resetFilters();
this.loadDropdown('hint', true);
}
checkDropdownToken(e) {
const input = e.target.value;
const { lastToken } = this.tokenizer.processTokens(input);
// Check for dropdown token
if (lastToken[lastToken.length - 1] === ':') {
const token = lastToken.slice(0, -1);
}
this.loadDropdown('hint');
}
checkForEnter(e) {
......
......@@ -18,7 +18,7 @@
= icon('times')
#js-dropdown-hint.dropdown-menu.hint-dropdown
%ul
%li.filter-dropdown-item{ 'data-value': 'none' }
%li.filter-dropdown-item{ 'data-value': '' }
%button.btn.btn-link
= icon('search')
%span
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment