2016-07-24 16:45:11 -04:00
|
|
|
(function() {
|
|
|
|
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
|
|
|
|
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
|
|
|
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
|
|
|
|
|
|
|
GitLabDropdownFilter = (function() {
|
|
|
|
var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
|
|
|
|
|
|
|
|
BLUR_KEYCODES = [27, 40];
|
|
|
|
|
|
|
|
ARROW_KEY_CODES = [38, 40];
|
|
|
|
|
|
|
|
HAS_VALUE_CLASS = "has-value";
|
|
|
|
|
|
|
|
function GitLabDropdownFilter(input, options) {
|
|
|
|
var $clearButton, $inputContainer, ref, timeout;
|
|
|
|
this.input = input;
|
|
|
|
this.options = options;
|
|
|
|
this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
|
|
|
|
$inputContainer = this.input.parent();
|
|
|
|
$clearButton = $inputContainer.find('.js-dropdown-input-clear');
|
|
|
|
this.indeterminateIds = [];
|
|
|
|
$clearButton.on('click', (function(_this) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Clear click
|
2016-07-24 16:45:11 -04:00
|
|
|
return function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
return _this.input.val('').trigger('keyup').focus();
|
|
|
|
};
|
|
|
|
})(this));
|
2016-07-26 23:32:10 -04:00
|
|
|
// Key events
|
2016-07-24 16:45:11 -04:00
|
|
|
timeout = "";
|
2016-08-03 04:08:24 -04:00
|
|
|
this.input
|
|
|
|
.on('keydown', function (e) {
|
|
|
|
var keyCode = e.which;
|
2016-08-19 04:45:58 -04:00
|
|
|
if (keyCode === 13 && !options.elIsInput) {
|
|
|
|
e.preventDefault()
|
2016-08-03 04:08:24 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.on('keyup', function(e) {
|
2016-07-24 16:45:11 -04:00
|
|
|
var keyCode;
|
|
|
|
keyCode = e.which;
|
|
|
|
if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) {
|
|
|
|
return;
|
|
|
|
}
|
2016-08-03 04:08:24 -04:00
|
|
|
if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
|
2016-07-24 16:45:11 -04:00
|
|
|
$inputContainer.addClass(HAS_VALUE_CLASS);
|
2016-08-03 04:08:24 -04:00
|
|
|
} else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
|
2016-07-24 16:45:11 -04:00
|
|
|
$inputContainer.removeClass(HAS_VALUE_CLASS);
|
|
|
|
}
|
2016-08-19 04:45:58 -04:00
|
|
|
if (keyCode === 13 && !options.elIsInput) {
|
2016-07-24 16:45:11 -04:00
|
|
|
return false;
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Only filter asynchronously only if option remote is set
|
2016-08-03 04:08:24 -04:00
|
|
|
if (this.options.remote) {
|
2016-07-24 16:45:11 -04:00
|
|
|
clearTimeout(timeout);
|
|
|
|
return timeout = setTimeout(function() {
|
2016-08-04 07:07:46 -04:00
|
|
|
var blurField = this.shouldBlur(keyCode);
|
|
|
|
if (blurField && this.filterInputBlur) {
|
2016-08-03 04:08:24 -04:00
|
|
|
this.input.blur();
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
2016-08-03 04:08:24 -04:00
|
|
|
return this.options.query(this.input.val(), function(data) {
|
|
|
|
return this.options.callback(data);
|
2016-08-03 04:31:36 -04:00
|
|
|
}.bind(this));
|
|
|
|
}.bind(this), 250);
|
2016-07-24 16:45:11 -04:00
|
|
|
} else {
|
2016-08-03 04:08:24 -04:00
|
|
|
return this.filter(this.input.val());
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
2016-08-03 04:08:24 -04:00
|
|
|
}.bind(this));
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
|
|
|
|
return BLUR_KEYCODES.indexOf(keyCode) >= 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdownFilter.prototype.filter = function(search_text) {
|
|
|
|
var data, elements, group, key, results, tmp;
|
|
|
|
if (this.options.onFilter) {
|
|
|
|
this.options.onFilter(search_text);
|
|
|
|
}
|
|
|
|
data = this.options.data();
|
|
|
|
if ((data != null) && !this.options.filterByText) {
|
|
|
|
results = data;
|
|
|
|
if (search_text !== '') {
|
2016-07-26 23:32:10 -04:00
|
|
|
// When data is an array of objects therefore [object Array] e.g.
|
|
|
|
// [
|
|
|
|
// { prop: 'foo' },
|
|
|
|
// { prop: 'baz' }
|
|
|
|
// ]
|
2016-07-24 16:45:11 -04:00
|
|
|
if (_.isArray(data)) {
|
|
|
|
results = fuzzaldrinPlus.filter(data, search_text, {
|
|
|
|
key: this.options.keys
|
|
|
|
});
|
|
|
|
} else {
|
2016-07-26 23:32:10 -04:00
|
|
|
// If data is grouped therefore an [object Object]. e.g.
|
|
|
|
// {
|
|
|
|
// groupName1: [
|
|
|
|
// { prop: 'foo' },
|
|
|
|
// { prop: 'baz' }
|
|
|
|
// ],
|
|
|
|
// groupName2: [
|
|
|
|
// { prop: 'abc' },
|
|
|
|
// { prop: 'def' }
|
|
|
|
// ]
|
|
|
|
// }
|
2016-07-24 16:45:11 -04:00
|
|
|
if (gl.utils.isObject(data)) {
|
|
|
|
results = {};
|
|
|
|
for (key in data) {
|
|
|
|
group = data[key];
|
|
|
|
tmp = fuzzaldrinPlus.filter(group, search_text, {
|
|
|
|
key: this.options.keys
|
|
|
|
});
|
|
|
|
if (tmp.length) {
|
|
|
|
results[key] = tmp.map(function(item) {
|
|
|
|
return item;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.options.callback(results);
|
|
|
|
} else {
|
|
|
|
elements = this.options.elements();
|
|
|
|
if (search_text) {
|
|
|
|
return elements.each(function() {
|
|
|
|
var $el, matches;
|
|
|
|
$el = $(this);
|
|
|
|
matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
|
|
|
|
if (!$el.is('.dropdown-header')) {
|
|
|
|
if (matches.length) {
|
2016-08-11 10:10:23 -04:00
|
|
|
return $el.show().removeClass('option-hidden');
|
2016-07-24 16:45:11 -04:00
|
|
|
} else {
|
2016-08-11 10:10:23 -04:00
|
|
|
return $el.hide().addClass('option-hidden');
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2016-08-24 10:51:33 -04:00
|
|
|
return elements.show().removeClass('option-hidden');
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return GitLabDropdownFilter;
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
GitLabDropdownRemote = (function() {
|
|
|
|
function GitLabDropdownRemote(dataEndpoint, options) {
|
|
|
|
this.dataEndpoint = dataEndpoint;
|
|
|
|
this.options = options;
|
|
|
|
}
|
|
|
|
|
|
|
|
GitLabDropdownRemote.prototype.execute = function() {
|
|
|
|
if (typeof this.dataEndpoint === "string") {
|
|
|
|
return this.fetchData();
|
|
|
|
} else if (typeof this.dataEndpoint === "function") {
|
|
|
|
if (this.options.beforeSend) {
|
|
|
|
this.options.beforeSend();
|
|
|
|
}
|
|
|
|
return this.dataEndpoint("", (function(_this) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Fetch the data by calling the data funcfion
|
2016-07-24 16:45:11 -04:00
|
|
|
return function(data) {
|
|
|
|
if (_this.options.success) {
|
|
|
|
_this.options.success(data);
|
|
|
|
}
|
|
|
|
if (_this.options.beforeSend) {
|
|
|
|
return _this.options.beforeSend();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})(this));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdownRemote.prototype.fetchData = function() {
|
|
|
|
return $.ajax({
|
|
|
|
url: this.dataEndpoint,
|
|
|
|
dataType: this.options.dataType,
|
|
|
|
beforeSend: (function(_this) {
|
|
|
|
return function() {
|
|
|
|
if (_this.options.beforeSend) {
|
|
|
|
return _this.options.beforeSend();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})(this),
|
|
|
|
success: (function(_this) {
|
|
|
|
return function(data) {
|
|
|
|
if (_this.options.success) {
|
|
|
|
return _this.options.success(data);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})(this)
|
|
|
|
});
|
2016-07-26 23:32:10 -04:00
|
|
|
// Fetch the data through ajax if the data is a string
|
2016-07-24 16:45:11 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
return GitLabDropdownRemote;
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
GitLabDropdown = (function() {
|
2016-08-19 12:17:17 -04:00
|
|
|
var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, currentIndex;
|
2016-07-24 16:45:11 -04:00
|
|
|
|
|
|
|
LOADING_CLASS = "is-loading";
|
|
|
|
|
|
|
|
PAGE_TWO_CLASS = "is-page-two";
|
|
|
|
|
|
|
|
ACTIVE_CLASS = "is-active";
|
|
|
|
|
|
|
|
INDETERMINATE_CLASS = "is-indeterminate";
|
|
|
|
|
|
|
|
currentIndex = -1;
|
|
|
|
|
2016-08-24 10:51:33 -04:00
|
|
|
NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
|
2016-07-26 20:17:07 -04:00
|
|
|
|
2016-08-24 10:51:33 -04:00
|
|
|
SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
|
2016-07-26 20:17:07 -04:00
|
|
|
|
|
|
|
CURSOR_SELECT_SCROLL_PADDING = 5
|
|
|
|
|
2016-07-24 16:45:11 -04:00
|
|
|
FILTER_INPUT = '.dropdown-input .dropdown-input-field';
|
|
|
|
|
|
|
|
function GitLabDropdown(el1, options) {
|
|
|
|
var ref, ref1, ref2, ref3, searchFields, selector, self;
|
|
|
|
this.el = el1;
|
|
|
|
this.options = options;
|
|
|
|
this.updateLabel = bind(this.updateLabel, this);
|
|
|
|
this.hidden = bind(this.hidden, this);
|
|
|
|
this.opened = bind(this.opened, this);
|
|
|
|
this.shouldPropagate = bind(this.shouldPropagate, this);
|
|
|
|
self = this;
|
|
|
|
selector = $(this.el).data("target");
|
|
|
|
this.dropdown = selector != null ? $(selector) : $(this.el).parent();
|
2016-07-26 23:32:10 -04:00
|
|
|
// Set Defaults
|
2016-07-24 16:45:11 -04:00
|
|
|
ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true;
|
2016-07-26 23:32:10 -04:00
|
|
|
// If no input is passed create a default one
|
2016-07-24 16:45:11 -04:00
|
|
|
self = this;
|
2016-07-26 23:32:10 -04:00
|
|
|
// If selector was passed
|
2016-07-24 16:45:11 -04:00
|
|
|
if (_.isString(this.filterInput)) {
|
|
|
|
this.filterInput = this.getElement(this.filterInput);
|
|
|
|
}
|
|
|
|
searchFields = this.options.search ? this.options.search.fields : [];
|
|
|
|
if (this.options.data) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// If we provided data
|
|
|
|
// data could be an array of objects or a group of arrays
|
2016-07-24 16:45:11 -04:00
|
|
|
if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
|
|
|
|
this.fullData = this.options.data;
|
2016-08-19 12:17:17 -04:00
|
|
|
currentIndex = -1;
|
2016-07-24 16:45:11 -04:00
|
|
|
this.parseData(this.options.data);
|
|
|
|
} else {
|
|
|
|
this.remote = new GitLabDropdownRemote(this.options.data, {
|
|
|
|
dataType: this.options.dataType,
|
|
|
|
beforeSend: this.toggleLoading.bind(this),
|
|
|
|
success: (function(_this) {
|
|
|
|
return function(data) {
|
|
|
|
_this.fullData = data;
|
|
|
|
_this.parseData(_this.fullData);
|
|
|
|
if (_this.options.filterable && _this.filter && _this.filter.input) {
|
|
|
|
return _this.filter.input.trigger('keyup');
|
|
|
|
}
|
|
|
|
};
|
2016-07-26 23:32:10 -04:00
|
|
|
// Remote data
|
2016-07-24 16:45:11 -04:00
|
|
|
})(this)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Init filterable
|
2016-07-24 16:45:11 -04:00
|
|
|
if (this.options.filterable) {
|
|
|
|
this.filter = new GitLabDropdownFilter(this.filterInput, {
|
2016-08-19 04:45:58 -04:00
|
|
|
elIsInput: $(this.el).is('input'),
|
2016-07-24 16:45:11 -04:00
|
|
|
filterInputBlur: this.filterInputBlur,
|
|
|
|
filterByText: this.options.filterByText,
|
|
|
|
onFilter: this.options.onFilter,
|
|
|
|
remote: this.options.filterRemote,
|
|
|
|
query: this.options.data,
|
|
|
|
keys: searchFields,
|
|
|
|
elements: (function(_this) {
|
|
|
|
return function() {
|
2016-08-11 10:10:23 -04:00
|
|
|
selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')';
|
2016-07-24 16:45:11 -04:00
|
|
|
if (_this.dropdown.find('.dropdown-toggle-page').length) {
|
|
|
|
selector = ".dropdown-page-one " + selector;
|
|
|
|
}
|
|
|
|
return $(selector);
|
|
|
|
};
|
|
|
|
})(this),
|
|
|
|
data: (function(_this) {
|
|
|
|
return function() {
|
|
|
|
return _this.fullData;
|
|
|
|
};
|
|
|
|
})(this),
|
|
|
|
callback: (function(_this) {
|
|
|
|
return function(data) {
|
|
|
|
_this.parseData(data);
|
|
|
|
if (_this.filterInput.val() !== '') {
|
2016-08-11 10:10:23 -04:00
|
|
|
selector = SELECTABLE_CLASSES;
|
2016-07-24 16:45:11 -04:00
|
|
|
if (_this.dropdown.find('.dropdown-toggle-page').length) {
|
|
|
|
selector = ".dropdown-page-one " + selector;
|
|
|
|
}
|
2016-08-19 04:45:58 -04:00
|
|
|
if ($(_this.el).is('input')) {
|
|
|
|
currentIndex = -1;
|
|
|
|
} else {
|
|
|
|
$(selector, _this.dropdown).first().find('a').addClass('is-focused');
|
|
|
|
currentIndex = 0;
|
|
|
|
}
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
})(this)
|
|
|
|
});
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Event listeners
|
2016-07-24 16:45:11 -04:00
|
|
|
this.dropdown.on("shown.bs.dropdown", this.opened);
|
|
|
|
this.dropdown.on("hidden.bs.dropdown", this.hidden);
|
|
|
|
$(this.el).on("update.label", this.updateLabel);
|
|
|
|
this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate);
|
|
|
|
this.dropdown.on('keyup', (function(_this) {
|
|
|
|
return function(e) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Escape key
|
2016-07-24 16:45:11 -04:00
|
|
|
if (e.which === 27) {
|
|
|
|
return $('.dropdown-menu-close', _this.dropdown).trigger('click');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})(this));
|
|
|
|
this.dropdown.on('blur', 'a', (function(_this) {
|
|
|
|
return function(e) {
|
|
|
|
var $dropdownMenu, $relatedTarget;
|
|
|
|
if (e.relatedTarget != null) {
|
|
|
|
$relatedTarget = $(e.relatedTarget);
|
|
|
|
$dropdownMenu = $relatedTarget.closest('.dropdown-menu');
|
|
|
|
if ($dropdownMenu.length === 0) {
|
|
|
|
return _this.dropdown.removeClass('open');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})(this));
|
|
|
|
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
|
|
|
this.dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on("click", (function(_this) {
|
|
|
|
return function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
return _this.togglePage();
|
|
|
|
};
|
|
|
|
})(this));
|
|
|
|
}
|
|
|
|
if (this.options.selectable) {
|
|
|
|
selector = ".dropdown-content a";
|
|
|
|
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
|
|
|
selector = ".dropdown-page-one .dropdown-content a";
|
|
|
|
}
|
|
|
|
this.dropdown.on("click", selector, function(e) {
|
|
|
|
var $el, selected;
|
|
|
|
$el = $(this);
|
|
|
|
selected = self.rowClicked($el);
|
|
|
|
if (self.options.clicked) {
|
|
|
|
self.options.clicked(selected, $el, e);
|
|
|
|
}
|
|
|
|
return $el.trigger('blur');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-26 23:32:10 -04:00
|
|
|
// Finds an element inside wrapper element
|
2016-07-24 16:45:11 -04:00
|
|
|
GitLabDropdown.prototype.getElement = function(selector) {
|
|
|
|
return this.dropdown.find(selector);
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.toggleLoading = function() {
|
|
|
|
return $('.dropdown-menu', this.dropdown).toggleClass(LOADING_CLASS);
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.togglePage = function() {
|
|
|
|
var menu;
|
|
|
|
menu = $('.dropdown-menu', this.dropdown);
|
|
|
|
if (menu.hasClass(PAGE_TWO_CLASS)) {
|
|
|
|
if (this.remote) {
|
|
|
|
this.remote.execute();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
menu.toggleClass(PAGE_TWO_CLASS);
|
2016-07-26 23:32:10 -04:00
|
|
|
// Focus first visible input on active page
|
2016-07-24 16:45:11 -04:00
|
|
|
return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.parseData = function(data) {
|
|
|
|
var full_html, groupData, html, name;
|
|
|
|
this.renderedData = data;
|
|
|
|
if (this.options.filterable && data.length === 0) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// render no matching results
|
2016-07-24 16:45:11 -04:00
|
|
|
html = [this.noResults()];
|
|
|
|
} else {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Handle array groups
|
2016-07-24 16:45:11 -04:00
|
|
|
if (gl.utils.isObject(data)) {
|
|
|
|
html = [];
|
|
|
|
for (name in data) {
|
|
|
|
groupData = data[name];
|
|
|
|
html.push(this.renderItem({
|
|
|
|
header: name
|
2016-07-26 23:32:10 -04:00
|
|
|
// Add header for each group
|
2016-07-24 16:45:11 -04:00
|
|
|
}, name));
|
|
|
|
this.renderData(groupData, name).map(function(item) {
|
|
|
|
return html.push(item);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Render each row
|
2016-07-24 16:45:11 -04:00
|
|
|
html = this.renderData(data);
|
|
|
|
}
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Render the full menu
|
2016-07-24 16:45:11 -04:00
|
|
|
full_html = this.renderMenu(html);
|
|
|
|
return this.appendMenu(full_html);
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.renderData = function(data, group) {
|
|
|
|
if (group == null) {
|
|
|
|
group = false;
|
|
|
|
}
|
|
|
|
return data.map((function(_this) {
|
|
|
|
return function(obj, index) {
|
|
|
|
return _this.renderItem(obj, group, index);
|
|
|
|
};
|
|
|
|
})(this));
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.shouldPropagate = function(e) {
|
|
|
|
var $target;
|
|
|
|
if (this.options.multiSelect) {
|
|
|
|
$target = $(e.target);
|
2016-07-26 20:17:07 -04:00
|
|
|
if ($target && !$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) {
|
2016-07-24 16:45:11 -04:00
|
|
|
e.stopPropagation();
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.opened = function() {
|
|
|
|
var contentHtml;
|
2016-07-26 20:17:07 -04:00
|
|
|
this.resetRows();
|
2016-07-24 16:45:11 -04:00
|
|
|
this.addArrowKeyEvent();
|
|
|
|
if (this.options.setIndeterminateIds) {
|
|
|
|
this.options.setIndeterminateIds.call(this);
|
|
|
|
}
|
|
|
|
if (this.options.setActiveIds) {
|
|
|
|
this.options.setActiveIds.call(this);
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Makes indeterminate items effective
|
2016-07-24 16:45:11 -04:00
|
|
|
if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
|
|
|
|
this.parseData(this.fullData);
|
|
|
|
}
|
|
|
|
contentHtml = $('.dropdown-content', this.dropdown).html();
|
|
|
|
if (this.remote && contentHtml === "") {
|
|
|
|
this.remote.execute();
|
|
|
|
}
|
|
|
|
if (this.options.filterable) {
|
|
|
|
this.filterInput.focus();
|
|
|
|
}
|
|
|
|
return this.dropdown.trigger('shown.gl.dropdown');
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.hidden = function(e) {
|
|
|
|
var $input;
|
2016-07-26 20:17:07 -04:00
|
|
|
this.resetRows();
|
2016-07-24 16:45:11 -04:00
|
|
|
this.removeArrayKeyEvent();
|
|
|
|
$input = this.dropdown.find(".dropdown-input-field");
|
|
|
|
if (this.options.filterable) {
|
|
|
|
$input.blur().val("");
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Triggering 'keyup' will re-render the dropdown which is not always required
|
|
|
|
// specially if we want to keep the state of the dropdown needed for bulk-assignment
|
2016-07-24 16:45:11 -04:00
|
|
|
if (!this.options.persistWhenHide) {
|
|
|
|
$input.trigger("keyup");
|
|
|
|
}
|
|
|
|
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
|
|
|
$('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
|
|
|
|
}
|
|
|
|
if (this.options.hidden) {
|
|
|
|
this.options.hidden.call(this, e);
|
|
|
|
}
|
|
|
|
return this.dropdown.trigger('hidden.gl.dropdown');
|
|
|
|
};
|
|
|
|
|
2016-07-26 23:32:10 -04:00
|
|
|
// Render the full menu
|
2016-07-24 16:45:11 -04:00
|
|
|
GitLabDropdown.prototype.renderMenu = function(html) {
|
|
|
|
var menu_html;
|
|
|
|
menu_html = "";
|
|
|
|
if (this.options.renderMenu) {
|
|
|
|
menu_html = this.options.renderMenu(html);
|
|
|
|
} else {
|
|
|
|
menu_html = $('<ul />').append(html);
|
|
|
|
}
|
|
|
|
return menu_html;
|
|
|
|
};
|
|
|
|
|
2016-07-26 23:32:10 -04:00
|
|
|
// Append the menu into the dropdown
|
2016-07-24 16:45:11 -04:00
|
|
|
GitLabDropdown.prototype.appendMenu = function(html) {
|
|
|
|
var selector;
|
|
|
|
selector = '.dropdown-content';
|
|
|
|
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
|
|
|
selector = ".dropdown-page-one .dropdown-content";
|
|
|
|
}
|
|
|
|
return $(selector, this.dropdown).empty().append(html);
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.renderItem = function(data, group, index) {
|
|
|
|
var cssClass, field, fieldName, groupAttrs, html, selected, text, url, value;
|
|
|
|
if (group == null) {
|
|
|
|
group = false;
|
|
|
|
}
|
|
|
|
if (index == null) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Render the row
|
2016-07-24 16:45:11 -04:00
|
|
|
index = false;
|
|
|
|
}
|
|
|
|
html = "";
|
2016-07-26 23:32:10 -04:00
|
|
|
// Divider
|
2016-07-24 16:45:11 -04:00
|
|
|
if (data === "divider") {
|
|
|
|
return "<li class='divider'></li>";
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Separator is a full-width divider
|
2016-07-24 16:45:11 -04:00
|
|
|
if (data === "separator") {
|
|
|
|
return "<li class='separator'></li>";
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Header
|
2016-07-24 16:45:11 -04:00
|
|
|
if (data.header != null) {
|
2016-07-26 20:17:07 -04:00
|
|
|
return _.template('<li class="dropdown-header"><%- header %></li>')({ header: data.header });
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
if (this.options.renderRow) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Call the render function
|
2016-07-24 16:45:11 -04:00
|
|
|
html = this.options.renderRow.call(this.options, data, this);
|
|
|
|
} else {
|
|
|
|
if (!selected) {
|
|
|
|
value = this.options.id ? this.options.id(data) : data.id;
|
2016-08-19 19:52:21 -04:00
|
|
|
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName;
|
2016-08-18 22:26:45 -04:00
|
|
|
|
2016-07-24 16:45:11 -04:00
|
|
|
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
|
|
|
|
if (field.length) {
|
|
|
|
selected = true;
|
|
|
|
}
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Set URL
|
2016-07-24 16:45:11 -04:00
|
|
|
if (this.options.url != null) {
|
|
|
|
url = this.options.url(data);
|
|
|
|
} else {
|
|
|
|
url = data.url != null ? data.url : '#';
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Set Text
|
2016-07-24 16:45:11 -04:00
|
|
|
if (this.options.text != null) {
|
|
|
|
text = this.options.text(data);
|
|
|
|
} else {
|
|
|
|
text = data.text != null ? data.text : '';
|
|
|
|
}
|
|
|
|
cssClass = "";
|
|
|
|
if (selected) {
|
|
|
|
cssClass = "is-active";
|
|
|
|
}
|
|
|
|
if (this.highlight) {
|
|
|
|
text = this.highlightTextMatches(text, this.filterInput.val());
|
|
|
|
}
|
|
|
|
if (group) {
|
2016-08-11 10:10:23 -04:00
|
|
|
groupAttrs = 'data-group=' + group + ' data-index=' + index;
|
2016-07-24 16:45:11 -04:00
|
|
|
} else {
|
|
|
|
groupAttrs = '';
|
|
|
|
}
|
2016-07-26 20:17:07 -04:00
|
|
|
html = _.template('<li><a href="<%- url %>" <%- groupAttrs %> class="<%- cssClass %>"><%= text %></a></li>')({
|
|
|
|
url: url,
|
|
|
|
groupAttrs: groupAttrs,
|
|
|
|
cssClass: cssClass,
|
|
|
|
text: text
|
|
|
|
});
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
return html;
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.highlightTextMatches = function(text, term) {
|
|
|
|
var occurrences;
|
|
|
|
occurrences = fuzzaldrinPlus.match(text, term);
|
|
|
|
return text.split('').map(function(character, i) {
|
|
|
|
if (indexOf.call(occurrences, i) >= 0) {
|
|
|
|
return "<b>" + character + "</b>";
|
|
|
|
} else {
|
|
|
|
return character;
|
|
|
|
}
|
|
|
|
}).join('');
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.noResults = function() {
|
|
|
|
var html;
|
|
|
|
return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>";
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.rowClicked = function(el) {
|
|
|
|
var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
|
|
|
|
isInput = $(this.el).is('input');
|
|
|
|
if (this.renderedData) {
|
|
|
|
groupName = el.data('group');
|
|
|
|
if (groupName) {
|
|
|
|
selectedIndex = el.data('index');
|
|
|
|
selectedObject = this.renderedData[groupName][selectedIndex];
|
|
|
|
} else {
|
|
|
|
selectedIndex = el.closest('li').index();
|
|
|
|
selectedObject = this.renderedData[selectedIndex];
|
|
|
|
}
|
|
|
|
}
|
2016-09-12 16:02:09 -04:00
|
|
|
field = [];
|
2016-08-19 19:52:21 -04:00
|
|
|
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName;
|
2016-07-24 16:45:11 -04:00
|
|
|
value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
|
|
|
|
if (isInput) {
|
|
|
|
field = $(this.el);
|
2016-09-12 16:02:09 -04:00
|
|
|
} else if(value) {
|
|
|
|
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
2016-09-12 16:02:09 -04:00
|
|
|
if (field.length && el.hasClass(ACTIVE_CLASS)) {
|
2016-07-24 16:45:11 -04:00
|
|
|
el.removeClass(ACTIVE_CLASS);
|
|
|
|
if (isInput) {
|
|
|
|
field.val('');
|
|
|
|
} else {
|
|
|
|
field.remove();
|
|
|
|
}
|
|
|
|
} else if (el.hasClass(INDETERMINATE_CLASS)) {
|
|
|
|
el.addClass(ACTIVE_CLASS);
|
|
|
|
el.removeClass(INDETERMINATE_CLASS);
|
2016-09-12 16:02:09 -04:00
|
|
|
if (field.length && value == null) {
|
2016-07-24 16:45:11 -04:00
|
|
|
field.remove();
|
|
|
|
}
|
|
|
|
if (!field.length && fieldName) {
|
2016-08-18 22:26:45 -04:00
|
|
|
this.addInput(fieldName, value, selectedObject);
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
|
|
|
|
this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
|
|
|
|
if (!isInput) {
|
|
|
|
this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
|
|
|
|
}
|
|
|
|
}
|
2016-09-12 16:02:09 -04:00
|
|
|
if (field.length && value == null) {
|
2016-07-24 16:45:11 -04:00
|
|
|
field.remove();
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// Toggle active class for the tick mark
|
2016-07-24 16:45:11 -04:00
|
|
|
el.addClass(ACTIVE_CLASS);
|
|
|
|
if (value != null) {
|
|
|
|
if (!field.length && fieldName) {
|
2016-08-18 22:26:45 -04:00
|
|
|
this.addInput(fieldName, value, selectedObject);
|
2016-09-12 16:02:09 -04:00
|
|
|
} else if (field.length) {
|
2016-07-24 16:45:11 -04:00
|
|
|
field.val(value).trigger('change');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-24 20:29:25 -04:00
|
|
|
|
|
|
|
// Update label right after input has been added
|
|
|
|
if (this.options.toggleLabel) {
|
|
|
|
this.updateLabel(selectedObject, el, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectedObject;
|
2016-07-24 16:45:11 -04:00
|
|
|
};
|
|
|
|
|
2016-08-18 22:26:45 -04:00
|
|
|
GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
|
2016-07-24 16:45:11 -04:00
|
|
|
var $input;
|
2016-07-26 23:32:10 -04:00
|
|
|
// Create hidden input for form
|
2016-07-24 16:45:11 -04:00
|
|
|
$input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
|
|
|
|
if (this.options.inputId != null) {
|
|
|
|
$input.attr('id', this.options.inputId);
|
|
|
|
}
|
2016-08-19 09:06:43 -04:00
|
|
|
if (selectedObject && selectedObject.type) {
|
2016-08-18 22:26:45 -04:00
|
|
|
$input.attr('data-type', selectedObject.type);
|
|
|
|
}
|
2016-07-24 16:45:11 -04:00
|
|
|
return this.dropdown.before($input);
|
|
|
|
};
|
|
|
|
|
2016-08-22 12:41:51 -04:00
|
|
|
GitLabDropdown.prototype.selectRowAtIndex = function(index) {
|
2016-07-24 16:45:11 -04:00
|
|
|
var $el, selector;
|
2016-08-22 12:41:51 -04:00
|
|
|
// If we pass an option index
|
|
|
|
if (typeof index !== "undefined") {
|
|
|
|
selector = SELECTABLE_CLASSES + ":eq(" + index + ") a";
|
|
|
|
} else {
|
|
|
|
selector = ".dropdown-content .is-focused";
|
|
|
|
}
|
2016-07-24 16:45:11 -04:00
|
|
|
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
|
|
|
selector = ".dropdown-page-one " + selector;
|
|
|
|
}
|
2016-07-26 23:32:10 -04:00
|
|
|
// simulate a click on the first link
|
2016-07-24 16:45:11 -04:00
|
|
|
$el = $(selector, this.dropdown);
|
|
|
|
if ($el.length) {
|
2016-08-11 10:10:23 -04:00
|
|
|
var href = $el.attr('href');
|
2016-08-19 04:45:58 -04:00
|
|
|
if (href && href !== '#') {
|
|
|
|
Turbolinks.visit(href);
|
|
|
|
} else {
|
|
|
|
$el.first().trigger('click');
|
|
|
|
}
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.addArrowKeyEvent = function() {
|
|
|
|
var $input, ARROW_KEY_CODES, selector;
|
|
|
|
ARROW_KEY_CODES = [38, 40];
|
|
|
|
$input = this.dropdown.find(".dropdown-input-field");
|
2016-07-26 20:17:07 -04:00
|
|
|
selector = SELECTABLE_CLASSES;
|
2016-07-24 16:45:11 -04:00
|
|
|
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
|
|
|
selector = ".dropdown-page-one " + selector;
|
|
|
|
}
|
|
|
|
return $('body').on('keydown', (function(_this) {
|
|
|
|
return function(e) {
|
|
|
|
var $listItems, PREV_INDEX, currentKeyCode;
|
|
|
|
currentKeyCode = e.which;
|
|
|
|
if (ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopImmediatePropagation();
|
|
|
|
PREV_INDEX = currentIndex;
|
|
|
|
$listItems = $(selector, _this.dropdown);
|
2016-07-26 23:32:10 -04:00
|
|
|
// if @options.filterable
|
|
|
|
// $input.blur()
|
2016-07-24 16:45:11 -04:00
|
|
|
if (currentKeyCode === 40) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Move down
|
2016-07-24 16:45:11 -04:00
|
|
|
if (currentIndex < ($listItems.length - 1)) {
|
|
|
|
currentIndex += 1;
|
|
|
|
}
|
|
|
|
} else if (currentKeyCode === 38) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Move up
|
2016-07-24 16:45:11 -04:00
|
|
|
if (currentIndex > 0) {
|
|
|
|
currentIndex -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (currentIndex !== PREV_INDEX) {
|
|
|
|
_this.highlightRowAtIndex($listItems, currentIndex);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (currentKeyCode === 13 && currentIndex !== -1) {
|
2016-08-19 04:45:58 -04:00
|
|
|
_this.selectRowAtIndex();
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
})(this));
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.removeArrayKeyEvent = function() {
|
|
|
|
return $('body').off('keydown');
|
|
|
|
};
|
|
|
|
|
2016-07-26 20:17:07 -04:00
|
|
|
GitLabDropdown.prototype.resetRows = function resetRows() {
|
2016-08-11 10:10:23 -04:00
|
|
|
currentIndex = -1;
|
|
|
|
$('.is-focused', this.dropdown).removeClass('is-focused');
|
2016-07-26 20:17:07 -04:00
|
|
|
};
|
|
|
|
|
2016-07-24 16:45:11 -04:00
|
|
|
GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
|
|
|
|
var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
|
2016-07-26 23:32:10 -04:00
|
|
|
// Remove the class for the previously focused row
|
2016-07-24 16:45:11 -04:00
|
|
|
$('.is-focused', this.dropdown).removeClass('is-focused');
|
2016-07-26 23:32:10 -04:00
|
|
|
// Update the class for the row at the specific index
|
2016-07-24 16:45:11 -04:00
|
|
|
$listItem = $listItems.eq(index);
|
|
|
|
$listItem.find('a:first-child').addClass("is-focused");
|
2016-07-26 23:32:10 -04:00
|
|
|
// Dropdown content scroll area
|
2016-07-24 16:45:11 -04:00
|
|
|
$dropdownContent = $listItem.closest('.dropdown-content');
|
|
|
|
dropdownScrollTop = $dropdownContent.scrollTop();
|
|
|
|
dropdownContentHeight = $dropdownContent.outerHeight();
|
|
|
|
dropdownContentTop = $dropdownContent.prop('offsetTop');
|
|
|
|
dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
|
2016-07-26 23:32:10 -04:00
|
|
|
// Get the offset bottom of the list item
|
2016-07-24 16:45:11 -04:00
|
|
|
listItemHeight = $listItem.outerHeight();
|
|
|
|
listItemTop = $listItem.prop('offsetTop');
|
|
|
|
listItemBottom = listItemTop + listItemHeight;
|
2016-07-26 20:17:07 -04:00
|
|
|
if (!index) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Scroll the dropdown content to the top
|
2016-07-26 20:17:07 -04:00
|
|
|
$dropdownContent.scrollTop(0)
|
2016-08-19 12:17:17 -04:00
|
|
|
} else if (index === ($listItems.length - 1)) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Scroll the dropdown content to the bottom
|
2016-08-19 12:17:17 -04:00
|
|
|
$dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
|
2016-08-11 10:10:23 -04:00
|
|
|
} else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Scroll the dropdown content down
|
2016-07-26 20:17:07 -04:00
|
|
|
$dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING);
|
|
|
|
} else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) {
|
2016-07-26 23:32:10 -04:00
|
|
|
// Scroll the dropdown content up
|
2016-07-26 20:17:07 -04:00
|
|
|
return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING);
|
2016-07-24 16:45:11 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
GitLabDropdown.prototype.updateLabel = function(selected, el, instance) {
|
|
|
|
if (selected == null) {
|
|
|
|
selected = null;
|
|
|
|
}
|
|
|
|
if (el == null) {
|
|
|
|
el = null;
|
|
|
|
}
|
|
|
|
if (instance == null) {
|
|
|
|
instance = null;
|
|
|
|
}
|
|
|
|
return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
|
|
|
|
};
|
|
|
|
|
|
|
|
return GitLabDropdown;
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
$.fn.glDropdown = function(opts) {
|
|
|
|
return this.each(function() {
|
|
|
|
if (!$.data(this, 'glDropdown')) {
|
|
|
|
return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
}).call(this);
|