392 lines
13 KiB
JavaScript
392 lines
13 KiB
JavaScript
(function() {
|
|
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
|
|
this.SearchAutocomplete = (function() {
|
|
var KEYCODE;
|
|
|
|
KEYCODE = {
|
|
ESCAPE: 27,
|
|
BACKSPACE: 8,
|
|
ENTER: 13,
|
|
UP: 38,
|
|
DOWN: 40
|
|
};
|
|
|
|
function SearchAutocomplete(opts) {
|
|
var ref, ref1, ref2, ref3, ref4;
|
|
if (opts == null) {
|
|
opts = {};
|
|
}
|
|
this.onSearchInputBlur = bind(this.onSearchInputBlur, this);
|
|
this.onClearInputClick = bind(this.onClearInputClick, this);
|
|
this.onSearchInputFocus = bind(this.onSearchInputFocus, this);
|
|
this.onSearchInputClick = bind(this.onSearchInputClick, this);
|
|
this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this);
|
|
this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this);
|
|
this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || '';
|
|
// Dropdown Element
|
|
this.dropdown = this.wrap.find('.dropdown');
|
|
this.dropdownContent = this.dropdown.find('.dropdown-content');
|
|
this.locationBadgeEl = this.getElement('.location-badge');
|
|
this.scopeInputEl = this.getElement('#scope');
|
|
this.searchInput = this.getElement('.search-input');
|
|
this.projectInputEl = this.getElement('#search_project_id');
|
|
this.groupInputEl = this.getElement('#group_id');
|
|
this.searchCodeInputEl = this.getElement('#search_code');
|
|
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();
|
|
}
|
|
|
|
// Finds an element inside wrapper element
|
|
SearchAutocomplete.prototype.getElement = function(selector) {
|
|
return this.wrap.find(selector);
|
|
};
|
|
|
|
SearchAutocomplete.prototype.saveOriginalState = function() {
|
|
return this.originalState = this.serializeState();
|
|
};
|
|
|
|
SearchAutocomplete.prototype.saveTextLength = function() {
|
|
return this.lastTextLength = this.searchInput.val().length;
|
|
};
|
|
|
|
SearchAutocomplete.prototype.createAutocomplete = function() {
|
|
return this.searchInput.glDropdown({
|
|
filterInputBlur: false,
|
|
filterable: true,
|
|
filterRemote: true,
|
|
highlight: true,
|
|
enterCallback: false,
|
|
filterInput: 'input#search',
|
|
search: {
|
|
fields: ['text']
|
|
},
|
|
data: this.getData.bind(this),
|
|
selectable: true,
|
|
clicked: this.onClick.bind(this)
|
|
});
|
|
};
|
|
|
|
SearchAutocomplete.prototype.getData = function(term, callback) {
|
|
var _this, contents, jqXHR;
|
|
_this = this;
|
|
if (!term) {
|
|
if (contents = this.getCategoryContents()) {
|
|
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, {
|
|
project_id: this.projectId,
|
|
project_ref: this.projectRef,
|
|
term: term
|
|
}, function(response) {
|
|
var data, firstCategory, i, lastCategory, len, suggestion;
|
|
// Hide dropdown menu if no suggestions returns
|
|
if (!response.length) {
|
|
_this.disableAutocomplete();
|
|
return;
|
|
}
|
|
data = [];
|
|
// List results
|
|
firstCategory = true;
|
|
for (i = 0, len = response.length; i < len; i++) {
|
|
suggestion = response[i];
|
|
// Add group header before list each group
|
|
if (lastCategory !== suggestion.category) {
|
|
if (!firstCategory) {
|
|
data.push('separator');
|
|
}
|
|
if (firstCategory) {
|
|
firstCategory = false;
|
|
}
|
|
data.push({
|
|
header: suggestion.category
|
|
});
|
|
lastCategory = suggestion.category;
|
|
}
|
|
data.push({
|
|
id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
|
|
category: suggestion.category,
|
|
text: suggestion.label,
|
|
url: suggestion.url
|
|
});
|
|
}
|
|
// Add option to proceed with the search
|
|
if (data.length) {
|
|
data.push('separator');
|
|
data.push({
|
|
text: "Result name contains \"" + term + "\"",
|
|
url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val())
|
|
});
|
|
}
|
|
return callback(data);
|
|
}).always(function() {
|
|
return _this.loadingSuggestions = false;
|
|
});
|
|
};
|
|
|
|
SearchAutocomplete.prototype.getCategoryContents = function() {
|
|
var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils;
|
|
userId = gon.current_user_id;
|
|
utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
|
|
if (utils.isInGroupsPage() && groupOptions) {
|
|
options = groupOptions[utils.getGroupSlug()];
|
|
} else if (utils.isInProjectPage() && projectOptions) {
|
|
options = projectOptions[utils.getProjectSlug()];
|
|
} else if (dashboardOptions) {
|
|
options = dashboardOptions;
|
|
}
|
|
issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name;
|
|
items = [
|
|
{
|
|
header: "" + name
|
|
}, {
|
|
text: 'Issues assigned to me',
|
|
url: issuesPath + "/?assignee_id=" + userId
|
|
}, {
|
|
text: "Issues I've created",
|
|
url: issuesPath + "/?author_id=" + userId
|
|
}, 'separator', {
|
|
text: 'Merge requests assigned to me',
|
|
url: mrPath + "/?assignee_id=" + userId
|
|
}, {
|
|
text: "Merge requests I've created",
|
|
url: mrPath + "/?author_id=" + userId
|
|
}
|
|
];
|
|
if (!name) {
|
|
items.splice(0, 1);
|
|
}
|
|
return items;
|
|
};
|
|
|
|
SearchAutocomplete.prototype.serializeState = function() {
|
|
return {
|
|
// Search Criteria
|
|
search_project_id: this.projectInputEl.val(),
|
|
group_id: this.groupInputEl.val(),
|
|
search_code: this.searchCodeInputEl.val(),
|
|
repository_ref: this.repositoryInputEl.val(),
|
|
scope: this.scopeInputEl.val(),
|
|
// Location badge
|
|
_location: this.locationBadgeEl.text()
|
|
};
|
|
};
|
|
|
|
SearchAutocomplete.prototype.bindEvents = function() {
|
|
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));
|
|
};
|
|
|
|
SearchAutocomplete.prototype.enableAutocomplete = function() {
|
|
var _this;
|
|
// No need to enable anything if user is not logged in
|
|
if (!gon.current_user_id) {
|
|
return;
|
|
}
|
|
if (!this.dropdown.hasClass('open')) {
|
|
_this = this;
|
|
this.loadingSuggestions = false;
|
|
this.dropdown.addClass('open').trigger('shown.bs.dropdown');
|
|
return this.searchInput.removeClass('disabled');
|
|
}
|
|
};
|
|
|
|
SearchAutocomplete.prototype.onSearchInputKeyDown = function() {
|
|
// Saves last length of the entered text
|
|
return this.saveTextLength();
|
|
};
|
|
|
|
SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) {
|
|
switch (e.keyCode) {
|
|
case KEYCODE.BACKSPACE:
|
|
// when trying to remove the location badge
|
|
if (this.lastTextLength === 0 && this.badgePresent()) {
|
|
this.removeLocationBadge();
|
|
}
|
|
// When removing the last character and no badge is present
|
|
if (this.lastTextLength === 1) {
|
|
this.disableAutocomplete();
|
|
}
|
|
// When removing any character from existin value
|
|
if (this.lastTextLength > 1) {
|
|
this.enableAutocomplete();
|
|
}
|
|
break;
|
|
case KEYCODE.ESCAPE:
|
|
this.restoreOriginalState();
|
|
break;
|
|
case KEYCODE.ENTER:
|
|
this.disableAutocomplete();
|
|
break;
|
|
case KEYCODE.UP:
|
|
case KEYCODE.DOWN:
|
|
return;
|
|
default:
|
|
// Handle the case when deleting the input value other than backspace
|
|
// e.g. Pressing ctrl + backspace or ctrl + x
|
|
if (this.searchInput.val() === '') {
|
|
this.disableAutocomplete();
|
|
} else {
|
|
// We should display the menu only when input is not empty
|
|
if (e.keyCode !== KEYCODE.ENTER) {
|
|
this.enableAutocomplete();
|
|
}
|
|
}
|
|
}
|
|
this.wrap.toggleClass('has-value', !!e.target.value);
|
|
};
|
|
|
|
// Avoid falsy value to be returned
|
|
SearchAutocomplete.prototype.onSearchInputClick = function(e) {
|
|
// Prevents closing the dropdown menu
|
|
return e.stopImmediatePropagation();
|
|
};
|
|
|
|
SearchAutocomplete.prototype.onSearchInputFocus = function() {
|
|
this.isFocused = true;
|
|
this.wrap.addClass('search-active');
|
|
if (this.getValue() === '') {
|
|
return this.getData();
|
|
}
|
|
};
|
|
|
|
SearchAutocomplete.prototype.getValue = function() {
|
|
return this.searchInput.val();
|
|
};
|
|
|
|
SearchAutocomplete.prototype.onClearInputClick = function(e) {
|
|
e.preventDefault();
|
|
return this.searchInput.val('').focus();
|
|
};
|
|
|
|
SearchAutocomplete.prototype.onSearchInputBlur = function(e) {
|
|
this.isFocused = false;
|
|
this.wrap.removeClass('search-active');
|
|
// If input is blank then restore state
|
|
if (this.searchInput.val() === '') {
|
|
return this.restoreOriginalState();
|
|
}
|
|
};
|
|
|
|
SearchAutocomplete.prototype.addLocationBadge = function(item) {
|
|
var badgeText, category, value;
|
|
category = item.category != null ? item.category + ": " : '';
|
|
value = item.value != null ? item.value : '';
|
|
badgeText = "" + category + value;
|
|
this.locationBadgeEl.text(badgeText).show();
|
|
return this.wrap.addClass('has-location-badge');
|
|
};
|
|
|
|
SearchAutocomplete.prototype.hasLocationBadge = function() {
|
|
return this.wrap.is('.has-location-badge');
|
|
};
|
|
|
|
SearchAutocomplete.prototype.restoreOriginalState = function() {
|
|
var i, input, inputs, len;
|
|
inputs = Object.keys(this.originalState);
|
|
for (i = 0, len = inputs.length; i < len; i++) {
|
|
input = inputs[i];
|
|
this.getElement("#" + input).val(this.originalState[input]);
|
|
}
|
|
if (this.originalState._location === '') {
|
|
return this.locationBadgeEl.hide();
|
|
} else {
|
|
return this.addLocationBadge({
|
|
value: this.originalState._location
|
|
});
|
|
}
|
|
};
|
|
|
|
SearchAutocomplete.prototype.badgePresent = function() {
|
|
return this.locationBadgeEl.length;
|
|
};
|
|
|
|
SearchAutocomplete.prototype.resetSearchState = function() {
|
|
var i, input, inputs, len, results;
|
|
inputs = Object.keys(this.originalState);
|
|
results = [];
|
|
for (i = 0, len = inputs.length; i < len; i++) {
|
|
input = inputs[i];
|
|
// _location isnt a input
|
|
if (input === '_location') {
|
|
break;
|
|
}
|
|
results.push(this.getElement("#" + input).val(''));
|
|
}
|
|
return results;
|
|
};
|
|
|
|
SearchAutocomplete.prototype.removeLocationBadge = function() {
|
|
this.locationBadgeEl.hide();
|
|
this.resetSearchState();
|
|
this.wrap.removeClass('has-location-badge');
|
|
return this.disableAutocomplete();
|
|
};
|
|
|
|
SearchAutocomplete.prototype.disableAutocomplete = function() {
|
|
if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
|
|
this.searchInput.addClass('disabled');
|
|
this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
|
|
this.restoreMenu();
|
|
}
|
|
};
|
|
|
|
SearchAutocomplete.prototype.restoreMenu = function() {
|
|
var html;
|
|
html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>";
|
|
return this.dropdownContent.html(html);
|
|
};
|
|
|
|
SearchAutocomplete.prototype.onClick = function(item, $el, e) {
|
|
if (location.pathname.indexOf(item.url) !== -1) {
|
|
e.preventDefault();
|
|
if (!this.badgePresent) {
|
|
if (item.category === 'Projects') {
|
|
this.projectInputEl.val(item.id);
|
|
this.addLocationBadge({
|
|
value: 'This project'
|
|
});
|
|
}
|
|
if (item.category === 'Groups') {
|
|
this.groupInputEl.val(item.id);
|
|
this.addLocationBadge({
|
|
value: 'This group'
|
|
});
|
|
}
|
|
}
|
|
$el.removeClass('is-active');
|
|
this.disableAutocomplete();
|
|
return this.searchInput.val('').focus();
|
|
}
|
|
};
|
|
|
|
return SearchAutocomplete;
|
|
|
|
})();
|
|
|
|
}).call(this);
|