summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-12-06 16:41:24 +0000
committerFilipa Lacerda <filipa@gitlab.com>2017-12-06 16:41:24 +0000
commit2c7ba7a1d85f33a357e197c0f9994029539b8989 (patch)
treedc2a76863214948f815e1b499e318b41014aaf6f
parentb4fb31d9dd12cf979d4014f028aa9ef2de342249 (diff)
downloadgitlab-ce-35773-search-box-close-dropdown.tar.gz
Updates the dropdown to match the docs and remove old hack of stop event propagation35773-search-box-close-dropdown
-rw-r--r--app/assets/javascripts/search_autocomplete.js143
-rw-r--r--app/views/layouts/_search.html.haml3
-rw-r--r--spec/javascripts/search_autocomplete_spec.js2
3 files changed, 77 insertions, 71 deletions
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 501815ecfac..e40a3596200 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -1,13 +1,20 @@
-/* eslint-disable comma-dangle, no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-unused-expressions, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
+/* eslint-disable no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils';
+/**
+ * Search input in top navigation bar.
+ * On click, opens a dropdown
+ * As the user types it filters the results
+ * When the user clicks `x` button it cleans the input and closes the dropdown.
+ */
+
((global) => {
const KEYCODE = {
ESCAPE: 27,
BACKSPACE: 8,
ENTER: 13,
UP: 38,
- DOWN: 40
+ DOWN: 40,
};
class SearchAutocomplete {
@@ -19,6 +26,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
this.dropdown = this.wrap.find('.dropdown');
+ this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
this.dropdownContent = this.dropdown.find('.dropdown-content');
this.locationBadgeEl = this.getElement('.location-badge');
this.scopeInputEl = this.getElement('#scope');
@@ -29,13 +37,16 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
this.repositoryInputEl = this.getElement('#repository_ref');
this.clearInput = this.getElement('.js-clear-input');
this.saveOriginalState();
+
// Only when user is logged in
if (gon.current_user_id) {
this.createAutocomplete();
}
+
this.searchInput.addClass('disabled');
this.saveTextLength();
this.bindEvents();
+ this.dropdownToggle.dropdown();
}
// Finds an element inside wrapper element
@@ -43,7 +54,6 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
this.onClearInputClick = this.onClearInputClick.bind(this);
this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
- this.onSearchInputClick = this.onSearchInputClick.bind(this);
this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
}
@@ -68,12 +78,12 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
enterCallback: false,
filterInput: 'input#search',
search: {
- fields: ['text']
+ fields: ['text'],
},
id: this.getSearchText,
data: this.getData.bind(this),
selectable: true,
- clicked: this.onClick.bind(this)
+ clicked: this.onClick.bind(this),
});
}
@@ -82,32 +92,35 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
}
getData(term, callback) {
- var _this, contents, jqXHR;
- _this = this;
if (!term) {
- if (contents = this.getCategoryContents()) {
+ const contents = this.getCategoryContents();
+ if (contents) {
this.searchInput.data('glDropdown').filter.options.callback(contents);
this.enableAutocomplete();
}
return;
}
+
// Prevent multiple ajax calls
if (this.loadingSuggestions) {
return;
}
+
this.loadingSuggestions = true;
- return jqXHR = $.get(this.autocompletePath, {
+
+ return $.get(this.autocompletePath, {
project_id: this.projectId,
project_ref: this.projectRef,
- term: term
- }, function(response) {
- var data, firstCategory, i, lastCategory, len, suggestion;
+ term: term,
+ }, (response) => {
+ var firstCategory, i, lastCategory, len, suggestion;
// Hide dropdown menu if no suggestions returns
if (!response.length) {
- _this.disableAutocomplete();
+ this.disableAutocomplete();
return;
}
- data = [];
+
+ const data = [];
// List results
firstCategory = true;
for (i = 0, len = response.length; i < len; i += 1) {
@@ -121,7 +134,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
firstCategory = false;
}
data.push({
- header: suggestion.category
+ header: suggestion.category,
});
lastCategory = suggestion.category;
}
@@ -129,7 +142,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
category: suggestion.category,
text: suggestion.label,
- url: suggestion.url
+ url: suggestion.url,
});
}
// Add option to proceed with the search
@@ -137,20 +150,21 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
data.push('separator');
data.push({
text: "Result name contains \"" + term + "\"",
- url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val())
+ url: "/search?search=" + term + "&project_id=" + (this.projectInputEl.val()) + "&group_id=" + (this.groupInputEl.val()),
});
}
return callback(data);
- }).always(function() {
- return _this.loadingSuggestions = false;
- });
+ })
+ .always(() => { this.loadingSuggestions = false; });
}
getCategoryContents() {
- var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName;
- userId = gon.current_user_id;
- userName = gon.current_username;
- projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
+ const userId = gon.current_user_id;
+ const userName = gon.current_username;
+ const { projectOptions, groupOptions, dashboardOptions } = gl;
+
+ // Get options
+ let options;
if (isInGroupsPage() && groupOptions) {
options = groupOptions[getGroupSlug()];
} else if (isInProjectPage() && projectOptions) {
@@ -158,37 +172,42 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
} else if (dashboardOptions) {
options = dashboardOptions;
}
- issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name;
- items = [
- {
- header: "" + name
- }
- ];
+
+ const { issuesPath, mrPath, name, issuesDisabled } = options;
+ const baseItems = [];
+
+ if (name) {
+ baseItems.push({
+ header: `${name}`,
+ });
+ }
+
const issueItems = [
{
text: 'Issues assigned to me',
- url: issuesPath + "/?assignee_username=" + userName
- }, {
+ url: `${issuesPath}/?assignee_username=${userName}`,
+ },
+ {
text: "Issues I've created",
- url: issuesPath + "/?author_username=" + userName
- }
+ url: `${issuesPath}/?author_username=${userName}`,
+ },
];
const mergeRequestItems = [
{
text: 'Merge requests assigned to me',
- url: mrPath + "/?assignee_username=" + userName
- }, {
+ url: `${mrPath}/?assignee_username=${userName}`,
+ },
+ {
text: "Merge requests I've created",
- url: mrPath + "/?author_username=" + userName
- }
+ url: `${mrPath}/?author_username=${userName}`,
+ },
];
- if (options.issuesDisabled) {
- items = items.concat(mergeRequestItems);
+
+ let items;
+ if (issuesDisabled) {
+ items = baseItems.concat(mergeRequestItems);
} else {
- items = items.concat(...issueItems, 'separator', ...mergeRequestItems);
- }
- if (!name) {
- items.splice(0, 1);
+ items = baseItems.concat(...issueItems, 'separator', ...mergeRequestItems);
}
return items;
}
@@ -202,39 +221,34 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
repository_ref: this.repositoryInputEl.val(),
scope: this.scopeInputEl.val(),
// Location badge
- _location: this.locationBadgeEl.text()
+ _location: this.locationBadgeEl.text(),
};
}
bindEvents() {
this.searchInput.on('keydown', this.onSearchInputKeyDown);
this.searchInput.on('keyup', this.onSearchInputKeyUp);
- this.searchInput.on('click', this.onSearchInputClick);
this.searchInput.on('focus', this.onSearchInputFocus);
this.searchInput.on('blur', this.onSearchInputBlur);
this.clearInput.on('click', this.onClearInputClick);
- return this.locationBadgeEl.on('click', (function(_this) {
- return function() {
- return _this.searchInput.focus();
- };
- })(this));
+ this.locationBadgeEl.on('click', () => this.searchInput.focus());
}
enableAutocomplete() {
- var _this;
// No need to enable anything if user is not logged in
if (!gon.current_user_id) {
return;
}
+
+ // If the dropdown is closed, we'll open it
if (!this.dropdown.hasClass('open')) {
- _this = this;
this.loadingSuggestions = false;
- this.dropdown.addClass('open').trigger('shown.bs.dropdown');
+ this.dropdownToggle.dropdown('toggle');
return this.searchInput.removeClass('disabled');
}
}
- // Saves last length of the entered text
+ // Saves last length of the entered text
onSearchInputKeyDown() {
return this.saveTextLength();
}
@@ -279,13 +293,6 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
this.wrap.toggleClass('has-value', !!e.target.value);
}
- // Avoid falsy value to be returned
- onSearchInputClick(e) {
- $('.dropdown').removeClass('open');
- this.dropdown.addClass('open');
- return e.stopImmediatePropagation();
- }
-
onSearchInputFocus() {
this.isFocused = true;
this.wrap.addClass('search-active');
@@ -337,7 +344,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
return this.locationBadgeEl.hide();
} else {
return this.addLocationBadge({
- value: this.originalState._location
+ value: this.originalState._location,
});
}
}
@@ -389,13 +396,13 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
if (item.category === 'Projects') {
this.projectInputEl.val(item.id);
this.addLocationBadge({
- value: 'This project'
+ value: 'This project',
});
}
if (item.category === 'Groups') {
this.groupInputEl.val(item.id);
this.addLocationBadge({
- value: 'This group'
+ value: 'This group',
});
}
}
@@ -422,7 +429,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
name: $projectOptionsDataEl.data('name'),
issuesPath: $projectOptionsDataEl.data('issues-path'),
issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
- mrPath: $projectOptionsDataEl.data('mr-path')
+ mrPath: $projectOptionsDataEl.data('mr-path'),
};
}
@@ -434,14 +441,14 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
gl.groupOptions[groupPath] = {
name: $groupOptionsDataEl.data('name'),
issuesPath: $groupOptionsDataEl.data('issues-path'),
- mrPath: $groupOptionsDataEl.data('mr-path')
+ mrPath: $groupOptionsDataEl.data('mr-path'),
};
}
if ($dashboardOptionsDataEl.length) {
gl.dashboardOptions = {
issuesPath: $dashboardOptionsDataEl.data('issues-path'),
- mrPath: $dashboardOptionsDataEl.data('mr-path')
+ mrPath: $dashboardOptionsDataEl.data('mr-path'),
};
}
});
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 4c5cc249159..1c211869cf8 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -13,7 +13,8 @@
.location-badge= label
.search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } }
- = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { toggle: 'dropdown', issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }, aria: { label: 'Search' }
+ = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }, aria: { label: 'Search' }
+ %button.hidden.js-dropdown-search-toggle{ data: { toggle: 'dropdown' }}
.dropdown-menu.dropdown-select
= dropdown_content do
%ul
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index a2394857b82..fdfc59a6f12 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -191,8 +191,6 @@ import '~/lib/utils/common_utils';
// browsers will not trigger default behavior (form submit, in this
// example) on JavaScript-created keypresses.
expect(submitSpy).not.toHaveBeenTriggered();
- // Does a worse job at capturing the intent of the test, but works.
- expect(enterKeyEvent.isDefaultPrevented()).toBe(true);
});
});
}).call(window);