summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Schatz <jschatz@gitlab.com>2017-04-26 20:12:34 +0000
committerJacob Schatz <jschatz@gitlab.com>2017-04-26 20:12:34 +0000
commitbdc4902fc4a20f6a113e03964a12e1836b01a723 (patch)
treef702f753d822d3c5ba3e9e92affa4290ccbb79c4
parente2be17b78e1d172ccfcee2de85a27d791284c720 (diff)
parent09f09139e482ea48c2807e8fc4fcbe8f292eb8eb (diff)
downloadgitlab-ce-bdc4902fc4a20f6a113e03964a12e1836b01a723.tar.gz
Merge branch '30466-click-x-to-remove-filter' into 'master'
Add button to delete filters from filtered search bar Closes #30466 See merge request !10795
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js38
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js14
-rw-r--r--app/assets/stylesheets/framework/filters.scss22
-rw-r--r--changelogs/unreleased/30466-click-x-to-remove-filter.yml4
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb2
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js63
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js16
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js7
8 files changed, 145 insertions, 21 deletions
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 68a832102a0..36af0674ac6 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -77,13 +77,14 @@ class FilteredSearchManager {
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.onClearSearchWrapper = this.onClearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
- this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
+ this.removeSelectedTokenKeydownWrapper = this.removeSelectedTokenKeydown.bind(this);
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this);
+ this.removeTokenWrapper = this.removeToken.bind(this);
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
@@ -96,12 +97,13 @@ class FilteredSearchManager {
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.addEventListener('click', this.removeTokenWrapper);
this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper);
document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.addEventListener('click', this.unselectEditTokensWrapper);
document.addEventListener('click', this.removeInputContainerFocusWrapper);
- document.addEventListener('keydown', this.removeSelectedTokenWrapper);
+ document.addEventListener('keydown', this.removeSelectedTokenKeydownWrapper);
eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
@@ -117,12 +119,13 @@ class FilteredSearchManager {
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.removeEventListener('click', this.removeTokenWrapper);
this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper);
document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.removeEventListener('click', this.unselectEditTokensWrapper);
document.removeEventListener('click', this.removeInputContainerFocusWrapper);
- document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
+ document.removeEventListener('keydown', this.removeSelectedTokenKeydownWrapper);
eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
@@ -195,14 +198,28 @@ class FilteredSearchManager {
static selectToken(e) {
const button = e.target.closest('.selectable');
+ const removeButtonSelected = e.target.closest('.remove-token');
- if (button) {
+ if (!removeButtonSelected && button) {
e.preventDefault();
e.stopPropagation();
gl.FilteredSearchVisualTokens.selectToken(button);
}
}
+ removeToken(e) {
+ const removeButtonSelected = e.target.closest('.remove-token');
+
+ if (removeButtonSelected) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const button = e.target.closest('.selectable');
+ gl.FilteredSearchVisualTokens.selectToken(button, true);
+ this.removeSelectedToken();
+ }
+ }
+
unselectEditTokens(e) {
const inputContainer = this.container.querySelector('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
@@ -248,16 +265,21 @@ class FilteredSearchManager {
}
}
- removeSelectedToken(e) {
+ removeSelectedTokenKeydown(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
- gl.FilteredSearchVisualTokens.removeSelectedToken();
- this.handleInputPlaceholder();
- this.toggleClearSearchButton();
+ this.removeSelectedToken();
}
}
+ removeSelectedToken() {
+ gl.FilteredSearchVisualTokens.removeSelectedToken();
+ this.handleInputPlaceholder();
+ this.toggleClearSearchButton();
+ this.dropdownManager.updateCurrentDropdownOffset();
+ }
+
onClearSearch(e) {
e.preventDefault();
this.clearSearch();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index a5657fc8720..453ecccc6fc 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -16,11 +16,11 @@ class FilteredSearchVisualTokens {
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
- static selectToken(tokenButton) {
+ static selectToken(tokenButton, forceSelection = false) {
const selected = tokenButton.classList.contains('selected');
FilteredSearchVisualTokens.unselectTokens();
- if (!selected) {
+ if (!selected || forceSelection) {
tokenButton.classList.add('selected');
}
}
@@ -38,7 +38,12 @@ class FilteredSearchVisualTokens {
return `
<div class="selectable" role="button">
<div class="name"></div>
- <div class="value"></div>
+ <div class="value-container">
+ <div class="value"></div>
+ <div class="remove-token" role="button">
+ <i class="fa fa-close"></i>
+ </div>
+ </div>
</div>
`;
}
@@ -122,7 +127,8 @@ class FilteredSearchVisualTokens {
if (value) {
const button = lastVisualToken.querySelector('.selectable');
- button.removeChild(value);
+ const valueContainer = lastVisualToken.querySelector('.value-container');
+ button.removeChild(valueContainer);
lastVisualToken.innerHTML = button.innerHTML;
} else {
lastVisualToken.closest('.tokens-container').removeChild(lastVisualToken);
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 11d44df4867..0692f65043b 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -104,6 +104,24 @@
padding: 2px 7px;
}
+ .value {
+ padding-right: 0;
+ }
+
+ .remove-token {
+ display: inline-block;
+ padding-left: 4px;
+ padding-right: 8px;
+
+ .fa-close {
+ color: $gl-text-color-disabled;
+ }
+
+ &:hover .fa-close {
+ color: $gl-text-color-secondary;
+ }
+ }
+
.name {
background-color: $filter-name-resting-color;
color: $filter-name-text-color;
@@ -112,7 +130,7 @@
text-transform: capitalize;
}
- .value {
+ .value-container {
background-color: $white-normal;
color: $filter-value-text-color;
border-radius: 0 2px 2px 0;
@@ -124,7 +142,7 @@
background-color: $filter-name-selected-color;
}
- .value {
+ .value-container {
background-color: $filter-value-selected-color;
}
}
diff --git a/changelogs/unreleased/30466-click-x-to-remove-filter.yml b/changelogs/unreleased/30466-click-x-to-remove-filter.yml
new file mode 100644
index 00000000000..2cf08e84ed1
--- /dev/null
+++ b/changelogs/unreleased/30466-click-x-to-remove-filter.yml
@@ -0,0 +1,4 @@
+---
+title: Add button to delete filters from filtered search bar
+merge_request:
+author:
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 81ee0e2e4f6..c824aa6a414 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -12,7 +12,7 @@ describe 'Filter issues', js: true, feature: true do
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
let!(:bug_label) { create(:label, project: project, title: 'bug') }
- let!(:caps_sensitive_label) { create(:label, project: project, title: 'CAPS_sensitive') }
+ let!(:caps_sensitive_label) { create(:label, project: project, title: 'CaPs') }
let!(:milestone) { create(:milestone, title: "8", project: project, start_date: 2.days.ago) }
let!(:multiple_words_label) { create(:label, project: project, title: "Two words") }
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 6683489f63c..e747aa497c2 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -26,6 +26,10 @@ describe('Filtered Search Manager', () => {
element.dispatchEvent(event);
}
+ function getVisualTokens() {
+ return tokensContainer.querySelectorAll('.js-visual-token');
+ }
+
beforeEach(() => {
setFixtures(`
<div class="filtered-search-box">
@@ -170,11 +174,37 @@ describe('Filtered Search Manager', () => {
});
});
- describe('removeSelectedToken', () => {
- function getVisualTokens() {
- return tokensContainer.querySelectorAll('.js-visual-token');
- }
+ describe('removeToken', () => {
+ it('removes token even when it is already selected', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
+ );
+
+ tokensContainer.querySelector('.js-visual-token .remove-token').click();
+ expect(tokensContainer.querySelector('.js-visual-token')).toEqual(null);
+ });
+ describe('unselected token', () => {
+ beforeEach(() => {
+ spyOn(gl.FilteredSearchManager.prototype, 'removeSelectedToken').and.callThrough();
+
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none'),
+ );
+ tokensContainer.querySelector('.js-visual-token .remove-token').click();
+ });
+
+ it('removes token when remove button is selected', () => {
+ expect(tokensContainer.querySelector('.js-visual-token')).toEqual(null);
+ });
+
+ it('calls removeSelectedToken', () => {
+ expect(manager.removeSelectedToken).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('removeSelectedTokenKeydown', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
@@ -224,6 +254,31 @@ describe('Filtered Search Manager', () => {
});
});
+ describe('removeSelectedToken', () => {
+ beforeEach(() => {
+ spyOn(gl.FilteredSearchVisualTokens, 'removeSelectedToken').and.callThrough();
+ spyOn(gl.FilteredSearchManager.prototype, 'handleInputPlaceholder').and.callThrough();
+ spyOn(gl.FilteredSearchManager.prototype, 'toggleClearSearchButton').and.callThrough();
+ manager.removeSelectedToken();
+ });
+
+ it('calls FilteredSearchVisualTokens.removeSelectedToken', () => {
+ expect(gl.FilteredSearchVisualTokens.removeSelectedToken).toHaveBeenCalled();
+ });
+
+ it('calls handleInputPlaceholder', () => {
+ expect(manager.handleInputPlaceholder).toHaveBeenCalled();
+ });
+
+ it('calls toggleClearSearchButton', () => {
+ expect(manager.toggleClearSearchButton).toHaveBeenCalled();
+ });
+
+ it('calls update dropdown offset', () => {
+ expect(manager.dropdownManager.updateDropdownOffset).toHaveBeenCalled();
+ });
+ });
+
describe('unselects token', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index bbda1476fed..d75b9061281 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -214,8 +214,12 @@ describe('Filtered Search Visual Tokens', () => {
expect(tokenElement.querySelector('.name')).toEqual(jasmine.anything());
});
+ it('contains value container div', () => {
+ expect(tokenElement.querySelector('.value-container')).toEqual(jasmine.anything());
+ });
+
it('contains value div', () => {
- expect(tokenElement.querySelector('.value')).toEqual(jasmine.anything());
+ expect(tokenElement.querySelector('.value-container .value')).toEqual(jasmine.anything());
});
it('contains selectable class', () => {
@@ -225,6 +229,16 @@ describe('Filtered Search Visual Tokens', () => {
it('contains button role', () => {
expect(tokenElement.getAttribute('role')).toEqual('button');
});
+
+ describe('remove token', () => {
+ it('contains remove-token button', () => {
+ expect(tokenElement.querySelector('.value-container .remove-token')).toEqual(jasmine.anything());
+ });
+
+ it('contains fa-close icon', () => {
+ expect(tokenElement.querySelector('.remove-token .fa-close')).toEqual(jasmine.anything());
+ });
+ });
});
describe('addVisualTokenElement', () => {
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
index ce83a256ddd..b8d4a93b1ab 100644
--- a/spec/javascripts/helpers/filtered_search_spec_helper.js
+++ b/spec/javascripts/helpers/filtered_search_spec_helper.js
@@ -10,7 +10,12 @@ class FilteredSearchSpecHelper {
li.innerHTML = `
<div class="selectable ${isSelected ? 'selected' : ''}" role="button">
<div class="name">${name}</div>
- <div class="value">${value}</div>
+ <div class="value-container">
+ <div class="value">${value}</div>
+ <div class="remove-token" role="button">
+ <i class="fa fa-close"></i>
+ </div>
+ </div>
</div>
`;