group links select2 infinite scroll
This commit is contained in:
parent
7faafa5ab5
commit
c5eb5aa998
5 changed files with 139 additions and 16 deletions
|
@ -6,23 +6,60 @@ var slice = [].slice;
|
|||
window.GroupsSelect = (function() {
|
||||
function GroupsSelect() {
|
||||
$('.ajax-groups-select').each((function(_this) {
|
||||
const self = _this;
|
||||
|
||||
return function(i, select) {
|
||||
var all_available, skip_groups;
|
||||
all_available = $(select).data('all-available');
|
||||
skip_groups = $(select).data('skip-groups') || [];
|
||||
return $(select).select2({
|
||||
const $select = $(select);
|
||||
all_available = $select.data('all-available');
|
||||
skip_groups = $select.data('skip-groups') || [];
|
||||
|
||||
$select.select2({
|
||||
placeholder: "Search for a group",
|
||||
multiple: $(select).hasClass('multiselect'),
|
||||
multiple: $select.hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
query: function(query) {
|
||||
var options = { all_available: all_available, skip_groups: skip_groups };
|
||||
return Api.groups(query.term, options, function(groups) {
|
||||
var data;
|
||||
data = {
|
||||
results: groups
|
||||
ajax: {
|
||||
url: Api.buildUrl(Api.groupsPath),
|
||||
dataType: 'json',
|
||||
quietMillis: 250,
|
||||
transport: function (params) {
|
||||
$.ajax(params).then((data, status, xhr) => {
|
||||
const results = data || [];
|
||||
|
||||
const headers = gl.utils.normalizeCRLFHeaders(xhr.getAllResponseHeaders());
|
||||
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
|
||||
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
|
||||
const more = currentPage < totalPages;
|
||||
|
||||
return {
|
||||
results,
|
||||
pagination: {
|
||||
more,
|
||||
},
|
||||
};
|
||||
}).then(params.success).fail(params.error);
|
||||
},
|
||||
data: function (search, page) {
|
||||
return {
|
||||
search,
|
||||
page,
|
||||
per_page: GroupsSelect.PER_PAGE,
|
||||
all_available,
|
||||
skip_groups,
|
||||
};
|
||||
return query.callback(data);
|
||||
});
|
||||
},
|
||||
results: function (data, page) {
|
||||
if (data.length) return { results: [] };
|
||||
|
||||
const results = data.length ? data : data.results || [];
|
||||
const more = data.pagination ? data.pagination.more : false;
|
||||
|
||||
return {
|
||||
results,
|
||||
page,
|
||||
more,
|
||||
};
|
||||
},
|
||||
},
|
||||
initSelection: function(element, callback) {
|
||||
var id;
|
||||
|
@ -34,19 +71,23 @@ window.GroupsSelect = (function() {
|
|||
formatResult: function() {
|
||||
var args;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
return _this.formatResult.apply(_this, args);
|
||||
return self.formatResult.apply(self, args);
|
||||
},
|
||||
formatSelection: function() {
|
||||
var args;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
return _this.formatSelection.apply(_this, args);
|
||||
return self.formatSelection.apply(self, args);
|
||||
},
|
||||
dropdownCssClass: "ajax-groups-dropdown",
|
||||
dropdownCssClass: "ajax-groups-dropdown select2-infinite",
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup: function(m) {
|
||||
return m;
|
||||
}
|
||||
});
|
||||
|
||||
self.dropdown = document.querySelector('.select2-infinite .select2-results');
|
||||
|
||||
$select.on('select2-loaded', self.forceOverflow.bind(self));
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
@ -65,5 +106,12 @@ window.GroupsSelect = (function() {
|
|||
return group.full_name;
|
||||
};
|
||||
|
||||
GroupsSelect.prototype.forceOverflow = function (e) {
|
||||
const itemHeight = this.dropdown.querySelector('.select2-result:first-child').clientHeight;
|
||||
this.dropdown.style.height = `${Math.floor(this.dropdown.scrollHeight - (itemHeight * 0.9))}px`;
|
||||
};
|
||||
|
||||
GroupsSelect.PER_PAGE = 20;
|
||||
|
||||
return GroupsSelect;
|
||||
})();
|
||||
|
|
|
@ -231,6 +231,22 @@
|
|||
return upperCaseHeaders;
|
||||
};
|
||||
|
||||
/**
|
||||
this will take in the getAllResponseHeaders result and normalize them
|
||||
this way we don't run into production issues when nginx gives us lowercased header keys
|
||||
*/
|
||||
w.gl.utils.normalizeCRLFHeaders = (headers) => {
|
||||
const headersObject = {};
|
||||
const headersArray = headers.split('\n');
|
||||
|
||||
headersArray.forEach((header) => {
|
||||
const keyValue = header.split(': ');
|
||||
headersObject[keyValue[0]] = keyValue[1];
|
||||
});
|
||||
|
||||
return w.gl.utils.normalizeHeaders(headersObject);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses pagination object string values into numbers.
|
||||
*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Project group links', feature: true, js: true do
|
||||
feature 'Project group links', :feature, :js do
|
||||
include Select2Helper
|
||||
|
||||
let(:master) { create(:user) }
|
||||
|
@ -51,4 +51,24 @@ feature 'Project group links', feature: true, js: true do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'the groups dropdown' do
|
||||
before do
|
||||
group_two = create(:group)
|
||||
group.add_owner(master)
|
||||
group_two.add_owner(master)
|
||||
|
||||
visit namespace_project_settings_members_path(project.namespace, project)
|
||||
execute_script 'GroupsSelect.PER_PAGE = 1;'
|
||||
open_select2 '#link_group_id'
|
||||
end
|
||||
|
||||
it 'should infinitely scroll' do
|
||||
expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 1)
|
||||
|
||||
scroll_select2_to_bottom('.select2-drop .select2-results:visible')
|
||||
|
||||
expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -108,6 +108,37 @@ require('~/lib/utils/common_utils');
|
|||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.normalizeCRLFHeaders', () => {
|
||||
beforeEach(function () {
|
||||
this.CLRFHeaders = 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE';
|
||||
|
||||
spyOn(String.prototype, 'split').and.callThrough();
|
||||
spyOn(gl.utils, 'normalizeHeaders').and.callThrough();
|
||||
|
||||
this.normalizeCRLFHeaders = gl.utils.normalizeCRLFHeaders(this.CLRFHeaders);
|
||||
});
|
||||
|
||||
it('should split by newline', function () {
|
||||
expect(String.prototype.split).toHaveBeenCalledWith('\n');
|
||||
});
|
||||
|
||||
it('should split by colon+space for each header', function () {
|
||||
expect(String.prototype.split.calls.allArgs().filter(args => args[0] === ': ').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should call gl.utils.normalizeHeaders with a parsed headers object', function () {
|
||||
expect(gl.utils.normalizeHeaders).toHaveBeenCalledWith(jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('should return a normalized headers object', function () {
|
||||
expect(this.normalizeCRLFHeaders).toEqual({
|
||||
'A-HEADER': 'a-value',
|
||||
'ANOTHER-HEADER': 'ANOTHER-VALUE',
|
||||
'LAST-HEADER': 'last-VALUE',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.parseIntPagination', () => {
|
||||
it('should parse to integers all string values and return pagination object', () => {
|
||||
const pagination = {
|
||||
|
|
|
@ -22,4 +22,12 @@ module Select2Helper
|
|||
execute_script("$('#{selector}').select2('val', '#{value}').trigger('change');")
|
||||
end
|
||||
end
|
||||
|
||||
def open_select2(selector)
|
||||
execute_script("$('#{selector}').select2('open');")
|
||||
end
|
||||
|
||||
def scroll_select2_to_bottom(selector)
|
||||
evaluate_script "$('#{selector}').scrollTop($('#{selector}')[0].scrollHeight); $('#{selector}');"
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue