Commit 743451b2 authored by Annabel Dunstone Gray's avatar Annabel Dunstone Gray

Merge branch '35773-search-box-close-dropdown' into 'master'

Close all open dropdowns when search input is clicked

Closes #35773

See merge request gitlab-org/gitlab-ce!15737
parents 6808d11b 67f20699
/* 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'; 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) => { ((global) => {
const KEYCODE = { const KEYCODE = {
ESCAPE: 27, ESCAPE: 27,
BACKSPACE: 8, BACKSPACE: 8,
ENTER: 13, ENTER: 13,
UP: 38, UP: 38,
DOWN: 40 DOWN: 40,
}; };
class SearchAutocomplete { class SearchAutocomplete {
...@@ -19,6 +26,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -19,6 +26,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || ''); this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || ''); this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
this.dropdown = this.wrap.find('.dropdown'); this.dropdown = this.wrap.find('.dropdown');
this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
this.dropdownContent = this.dropdown.find('.dropdown-content'); this.dropdownContent = this.dropdown.find('.dropdown-content');
this.locationBadgeEl = this.getElement('.location-badge'); this.locationBadgeEl = this.getElement('.location-badge');
this.scopeInputEl = this.getElement('#scope'); this.scopeInputEl = this.getElement('#scope');
...@@ -29,13 +37,16 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -29,13 +37,16 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
this.repositoryInputEl = this.getElement('#repository_ref'); this.repositoryInputEl = this.getElement('#repository_ref');
this.clearInput = this.getElement('.js-clear-input'); this.clearInput = this.getElement('.js-clear-input');
this.saveOriginalState(); this.saveOriginalState();
// Only when user is logged in // Only when user is logged in
if (gon.current_user_id) { if (gon.current_user_id) {
this.createAutocomplete(); this.createAutocomplete();
} }
this.searchInput.addClass('disabled'); this.searchInput.addClass('disabled');
this.saveTextLength(); this.saveTextLength();
this.bindEvents(); this.bindEvents();
this.dropdownToggle.dropdown();
} }
// Finds an element inside wrapper element // Finds an element inside wrapper element
...@@ -43,7 +54,6 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -43,7 +54,6 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
this.onSearchInputBlur = this.onSearchInputBlur.bind(this); this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
this.onClearInputClick = this.onClearInputClick.bind(this); this.onClearInputClick = this.onClearInputClick.bind(this);
this.onSearchInputFocus = this.onSearchInputFocus.bind(this); this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
this.onSearchInputClick = this.onSearchInputClick.bind(this);
this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
} }
...@@ -68,12 +78,12 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -68,12 +78,12 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
enterCallback: false, enterCallback: false,
filterInput: 'input#search', filterInput: 'input#search',
search: { search: {
fields: ['text'] fields: ['text'],
}, },
id: this.getSearchText, id: this.getSearchText,
data: this.getData.bind(this), data: this.getData.bind(this),
selectable: true, selectable: true,
clicked: this.onClick.bind(this) clicked: this.onClick.bind(this),
}); });
} }
...@@ -82,32 +92,35 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -82,32 +92,35 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
} }
getData(term, callback) { getData(term, callback) {
var _this, contents, jqXHR;
_this = this;
if (!term) { if (!term) {
if (contents = this.getCategoryContents()) { const contents = this.getCategoryContents();
if (contents) {
this.searchInput.data('glDropdown').filter.options.callback(contents); this.searchInput.data('glDropdown').filter.options.callback(contents);
this.enableAutocomplete(); this.enableAutocomplete();
} }
return; return;
} }
// Prevent multiple ajax calls // Prevent multiple ajax calls
if (this.loadingSuggestions) { if (this.loadingSuggestions) {
return; return;
} }
this.loadingSuggestions = true; this.loadingSuggestions = true;
return jqXHR = $.get(this.autocompletePath, {
return $.get(this.autocompletePath, {
project_id: this.projectId, project_id: this.projectId,
project_ref: this.projectRef, project_ref: this.projectRef,
term: term term: term,
}, function(response) { }, (response) => {
var data, firstCategory, i, lastCategory, len, suggestion; var firstCategory, i, lastCategory, len, suggestion;
// Hide dropdown menu if no suggestions returns // Hide dropdown menu if no suggestions returns
if (!response.length) { if (!response.length) {
_this.disableAutocomplete(); this.disableAutocomplete();
return; return;
} }
data = [];
const data = [];
// List results // List results
firstCategory = true; firstCategory = true;
for (i = 0, len = response.length; i < len; i += 1) { for (i = 0, len = response.length; i < len; i += 1) {
...@@ -121,7 +134,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -121,7 +134,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
firstCategory = false; firstCategory = false;
} }
data.push({ data.push({
header: suggestion.category header: suggestion.category,
}); });
lastCategory = suggestion.category; lastCategory = suggestion.category;
} }
...@@ -129,7 +142,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -129,7 +142,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
id: (suggestion.category.toLowerCase()) + "-" + suggestion.id, id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
category: suggestion.category, category: suggestion.category,
text: suggestion.label, text: suggestion.label,
url: suggestion.url url: suggestion.url,
}); });
} }
// Add option to proceed with the search // Add option to proceed with the search
...@@ -137,20 +150,21 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -137,20 +150,21 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
data.push('separator'); data.push('separator');
data.push({ data.push({
text: "Result name contains \"" + term + "\"", 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); return callback(data);
}).always(function() { })
return _this.loadingSuggestions = false; .always(() => { this.loadingSuggestions = false; });
});
} }
getCategoryContents() { getCategoryContents() {
var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName; const userId = gon.current_user_id;
userId = gon.current_user_id; const userName = gon.current_username;
userName = gon.current_username; const { projectOptions, groupOptions, dashboardOptions } = gl;
projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
// Get options
let options;
if (isInGroupsPage() && groupOptions) { if (isInGroupsPage() && groupOptions) {
options = groupOptions[getGroupSlug()]; options = groupOptions[getGroupSlug()];
} else if (isInProjectPage() && projectOptions) { } else if (isInProjectPage() && projectOptions) {
...@@ -158,37 +172,42 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -158,37 +172,42 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
} else if (dashboardOptions) { } else if (dashboardOptions) {
options = dashboardOptions; options = dashboardOptions;
} }
issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name;
items = [ const { issuesPath, mrPath, name, issuesDisabled } = options;
{ const baseItems = [];
header: "" + name
if (name) {
baseItems.push({
header: `${name}`,
});
} }
];
const issueItems = [ const issueItems = [
{ {
text: 'Issues assigned to me', text: 'Issues assigned to me',
url: issuesPath + "/?assignee_username=" + userName url: `${issuesPath}/?assignee_username=${userName}`,
}, { },
{
text: "Issues I've created", text: "Issues I've created",
url: issuesPath + "/?author_username=" + userName url: `${issuesPath}/?author_username=${userName}`,
} },
]; ];
const mergeRequestItems = [ const mergeRequestItems = [
{ {
text: 'Merge requests assigned to me', text: 'Merge requests assigned to me',
url: mrPath + "/?assignee_username=" + userName url: `${mrPath}/?assignee_username=${userName}`,
}, { },
{
text: "Merge requests I've created", 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 { } else {
items = items.concat(...issueItems, 'separator', ...mergeRequestItems); items = baseItems.concat(...issueItems, 'separator', ...mergeRequestItems);
}
if (!name) {
items.splice(0, 1);
} }
return items; return items;
} }
...@@ -202,34 +221,29 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -202,34 +221,29 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
repository_ref: this.repositoryInputEl.val(), repository_ref: this.repositoryInputEl.val(),
scope: this.scopeInputEl.val(), scope: this.scopeInputEl.val(),
// Location badge // Location badge
_location: this.locationBadgeEl.text() _location: this.locationBadgeEl.text(),
}; };
} }
bindEvents() { bindEvents() {
this.searchInput.on('keydown', this.onSearchInputKeyDown); this.searchInput.on('keydown', this.onSearchInputKeyDown);
this.searchInput.on('keyup', this.onSearchInputKeyUp); this.searchInput.on('keyup', this.onSearchInputKeyUp);
this.searchInput.on('click', this.onSearchInputClick);
this.searchInput.on('focus', this.onSearchInputFocus); this.searchInput.on('focus', this.onSearchInputFocus);
this.searchInput.on('blur', this.onSearchInputBlur); this.searchInput.on('blur', this.onSearchInputBlur);
this.clearInput.on('click', this.onClearInputClick); this.clearInput.on('click', this.onClearInputClick);
return this.locationBadgeEl.on('click', (function(_this) { this.locationBadgeEl.on('click', () => this.searchInput.focus());
return function() {
return _this.searchInput.focus();
};
})(this));
} }
enableAutocomplete() { enableAutocomplete() {
var _this;
// No need to enable anything if user is not logged in // No need to enable anything if user is not logged in
if (!gon.current_user_id) { if (!gon.current_user_id) {
return; return;
} }
// If the dropdown is closed, we'll open it
if (!this.dropdown.hasClass('open')) { if (!this.dropdown.hasClass('open')) {
_this = this;
this.loadingSuggestions = false; this.loadingSuggestions = false;
this.dropdown.addClass('open').trigger('shown.bs.dropdown'); this.dropdownToggle.dropdown('toggle');
return this.searchInput.removeClass('disabled'); return this.searchInput.removeClass('disabled');
} }
} }
...@@ -279,11 +293,6 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -279,11 +293,6 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
this.wrap.toggleClass('has-value', !!e.target.value); this.wrap.toggleClass('has-value', !!e.target.value);
} }
// Avoid falsy value to be returned
onSearchInputClick(e) {
return e.stopImmediatePropagation();
}
onSearchInputFocus() { onSearchInputFocus() {
this.isFocused = true; this.isFocused = true;
this.wrap.addClass('search-active'); this.wrap.addClass('search-active');
...@@ -335,7 +344,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -335,7 +344,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
return this.locationBadgeEl.hide(); return this.locationBadgeEl.hide();
} else { } else {
return this.addLocationBadge({ return this.addLocationBadge({
value: this.originalState._location value: this.originalState._location,
}); });
} }
} }
...@@ -387,13 +396,13 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -387,13 +396,13 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
if (item.category === 'Projects') { if (item.category === 'Projects') {
this.projectInputEl.val(item.id); this.projectInputEl.val(item.id);
this.addLocationBadge({ this.addLocationBadge({
value: 'This project' value: 'This project',
}); });
} }
if (item.category === 'Groups') { if (item.category === 'Groups') {
this.groupInputEl.val(item.id); this.groupInputEl.val(item.id);
this.addLocationBadge({ this.addLocationBadge({
value: 'This group' value: 'This group',
}); });
} }
} }
...@@ -420,7 +429,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -420,7 +429,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
name: $projectOptionsDataEl.data('name'), name: $projectOptionsDataEl.data('name'),
issuesPath: $projectOptionsDataEl.data('issues-path'), issuesPath: $projectOptionsDataEl.data('issues-path'),
issuesDisabled: $projectOptionsDataEl.data('issues-disabled'), issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
mrPath: $projectOptionsDataEl.data('mr-path') mrPath: $projectOptionsDataEl.data('mr-path'),
}; };
} }
...@@ -432,14 +441,14 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '. ...@@ -432,14 +441,14 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
gl.groupOptions[groupPath] = { gl.groupOptions[groupPath] = {
name: $groupOptionsDataEl.data('name'), name: $groupOptionsDataEl.data('name'),
issuesPath: $groupOptionsDataEl.data('issues-path'), issuesPath: $groupOptionsDataEl.data('issues-path'),
mrPath: $groupOptionsDataEl.data('mr-path') mrPath: $groupOptionsDataEl.data('mr-path'),
}; };
} }
if ($dashboardOptionsDataEl.length) { if ($dashboardOptionsDataEl.length) {
gl.dashboardOptions = { gl.dashboardOptions = {
issuesPath: $dashboardOptionsDataEl.data('issues-path'), issuesPath: $dashboardOptionsDataEl.data('issues-path'),
mrPath: $dashboardOptionsDataEl.data('mr-path') mrPath: $dashboardOptionsDataEl.data('mr-path'),
}; };
} }
}); });
......
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
.location-badge= label .location-badge= label
.search-input-wrap .search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } } .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{ type: 'button', data: { toggle: 'dropdown' } }
.dropdown-menu.dropdown-select .dropdown-menu.dropdown-select
= dropdown_content do = dropdown_content do
%ul %ul
......
...@@ -191,8 +191,6 @@ import '~/lib/utils/common_utils'; ...@@ -191,8 +191,6 @@ import '~/lib/utils/common_utils';
// browsers will not trigger default behavior (form submit, in this // browsers will not trigger default behavior (form submit, in this
// example) on JavaScript-created keypresses. // example) on JavaScript-created keypresses.
expect(submitSpy).not.toHaveBeenTriggered(); expect(submitSpy).not.toHaveBeenTriggered();
// Does a worse job at capturing the intent of the test, but works.
expect(enterKeyEvent.isDefaultPrevented()).toBe(true);
}); });
}); });
}).call(window); }).call(window);
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