diff options
author | Alfredo Sumaran <alfredo@gitlab.com> | 2017-06-06 00:06:08 -0500 |
---|---|---|
committer | Alfredo Sumaran <alfredo@gitlab.com> | 2017-06-06 04:31:40 -0500 |
commit | 323a326c73f4aabf37bf79f8e42350c128983c2d (patch) | |
tree | fb21b276fac004c817871c68af5a349bc8ed012f /app | |
parent | ea531e1effa51bcec84e50a69901e6eec7c789c1 (diff) | |
download | gitlab-ce-323a326c73f4aabf37bf79f8e42350c128983c2d.tar.gz |
Improve pagination when searching or filtering
[ci skip]
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/filterable_list.js | 62 | ||||
-rw-r--r-- | app/assets/javascripts/groups/components/groups.vue | 12 | ||||
-rw-r--r-- | app/assets/javascripts/groups/groups_filterable_list.js | 57 | ||||
-rw-r--r-- | app/assets/javascripts/groups/index.js | 35 | ||||
-rw-r--r-- | app/assets/javascripts/groups/services/groups_service.js | 6 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/common_utils.js | 4 | ||||
-rw-r--r-- | app/views/dashboard/groups/_groups.html.haml | 2 |
7 files changed, 130 insertions, 48 deletions
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js index 17c39cc7bbb..139206cc185 100644 --- a/app/assets/javascripts/filterable_list.js +++ b/app/assets/javascripts/filterable_list.js @@ -8,7 +8,15 @@ export default class FilterableList { this.filterForm = form; this.listFilterElement = filter; this.listHolderElement = holder; - this.filterUrl = `${this.filterForm.getAttribute('action')}?${$(this.filterForm).serialize()}`; + this.isBusy = false; + } + + getFilterEndpoint() { + return `${this.filterForm.getAttribute('action')}?${$(this.filterForm).serialize()}`; + } + + getPagePath() { + return this.getFilterEndpoint(); } initSearch() { @@ -20,9 +28,19 @@ export default class FilterableList { } onFilterInput() { - const url = this.filterForm.getAttribute('action'); - const data = $(this.filterForm).serialize(); - this.filterResults(url, data, 'filter-input'); + const $form = $(this.filterForm); + const queryData = {}; + const filterGroupsParam = $form.find('[name="filter_groups"]').val(); + + if (filterGroupsParam) { + queryData.filter_groups = filterGroupsParam; + } + + this.filterResults(queryData); + + if (this.setDefaultFilterOption) { + this.setDefaultFilterOption(); + } } bindEvents() { @@ -33,42 +51,44 @@ export default class FilterableList { this.listFilterElement.removeEventListener('input', this.debounceFilter); } - filterResults(url, data, comingFrom) { - const endpoint = url || this.filterForm.getAttribute('action'); - const additionalData = data || $(this.filterForm).serialize(); + filterResults(queryData) { + if (this.isBusy) { + return false; + } $(this.listHolderElement).fadeTo(250, 0.5); return $.ajax({ - url: endpoint, - data: additionalData, + url: this.getFilterEndpoint(), + data: queryData, type: 'GET', dataType: 'json', context: this, complete: this.onFilterComplete, + beforeSend: () => { + this.isBusy = true; + }, success: (response, textStatus, xhr) => { - if (this.preOnFilterSuccess) { - this.preOnFilterSuccess(comingFrom); - } - - this.onFilterSuccess(response, xhr); + this.onFilterSuccess(response, xhr, queryData); }, }); } - onFilterSuccess(data) { - if (data.html) { - this.listHolderElement.innerHTML = data.html; + onFilterSuccess(response, xhr, queryData) { + if (response.html) { + this.listHolderElement.innerHTML = response.html; } - // Change url so if user reload a page - search results are saved - return window.history.replaceState({ - page: this.filterUrl, + // Change url so if user reload a page - search results are saved + const currentPath = this.getPagePath(queryData); - }, document.title, this.filterUrl); + return window.history.replaceState({ + page: currentPath, + }, document.title, currentPath); } onFilterComplete() { + this.isBusy = false; $(this.listHolderElement).fadeTo(250, 1); } } diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue index 6ddf54275d9..759b68c8bb4 100644 --- a/app/assets/javascripts/groups/components/groups.vue +++ b/app/assets/javascripts/groups/components/groups.vue @@ -1,5 +1,6 @@ <script> -import TablePaginationComponent from '~/vue_shared/components/table_pagination'; +import TablePaginationComponent from '~/vue_shared/components/table_pagination.vue'; +import eventHub from '../event_hub'; export default { components: { @@ -16,11 +17,10 @@ export default { }, }, methods: { - change(pageNumber) { - const param = gl.utils.setParamInURL('page', pageNumber); - - gl.utils.visitUrl(param); - return param; + change(page) { + const filterGroupsParam = gl.utils.getParameterByName('filter_groups'); + const sortParam = gl.utils.getParameterByName('sort'); + eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam); }, }, }; diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js index f3c1b8ab545..439a931ddad 100644 --- a/app/assets/javascripts/groups/groups_filterable_list.js +++ b/app/assets/javascripts/groups/groups_filterable_list.js @@ -2,12 +2,24 @@ import FilterableList from '~/filterable_list'; import eventHub from './event_hub'; export default class GroupFilterableList extends FilterableList { - constructor(form, filter, holder) { + constructor({ form, filter, holder, filterEndpoint, pagePath }) { super(form, filter, holder); - + this.form = form; + this.filterEndpoint = filterEndpoint; + this.pagePath = pagePath; this.$dropdown = $('.js-group-filter-dropdown-wrap'); } + getFilterEndpoint() { + return this.filterEndpoint; + } + + getPagePath(queryData) { + const params = queryData ? $.param(queryData) : ''; + const queryString = params ? `?${params}` : ''; + return `${this.pagePath}${queryString}`; + } + bindEvents() { super.bindEvents(); @@ -21,26 +33,45 @@ export default class GroupFilterableList extends FilterableList { onFormSubmit(e) { e.preventDefault(); - this.filterResults(); + const $form = $(this.form); + const filterGroupsParam = $form.find('[name="filter_groups"]').val(); + const queryData = {}; + + if (filterGroupsParam) { + queryData.filter_groups = filterGroupsParam; + } + + this.filterResults(queryData); + this.setDefaultFilterOption(); + } + + setDefaultFilterOption() { + const defaultOption = $.trim(this.$dropdown.find('.dropdown-menu a:first-child').text()); + this.$dropdown.find('.dropdown-label').text(defaultOption); } onOptionClick(e) { e.preventDefault(); - const currentOption = $.trim(e.currentTarget.text); - this.filterUrl = e.currentTarget.href; - this.$dropdown.find('.dropdown-label').text(currentOption); - this.filterResults(this.filterUrl); - } + const queryData = {}; + const sortParam = gl.utils.getParameterByName('sort', e.currentTarget.href); - preOnFilterSuccess(comingFrom) { - if (comingFrom === 'filter-input') { - this.filterUrl = `${this.filterForm.getAttribute('action')}?${$(this.filterForm).serialize()}`; + if (sortParam) { + queryData.sort = sortParam; } + + this.filterResults(queryData); + + // Active selected option + this.$dropdown.find('.dropdown-label').text($.trim(e.currentTarget.text)); + + // Clear current value on search form + this.form.querySelector('[name="filter_groups"]').value = ''; } - onFilterSuccess(data, xhr) { - super.onFilterSuccess(data); + onFilterSuccess(data, xhr, queryData) { + super.onFilterSuccess(data, xhr, queryData); + const paginationData = { 'X-Per-Page': xhr.getResponseHeader('X-Per-Page'), 'X-Page': xhr.getResponseHeader('X-Page'), diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js index 21a1c71ca77..9c2e37dd5f1 100644 --- a/app/assets/javascripts/groups/index.js +++ b/app/assets/javascripts/groups/index.js @@ -37,7 +37,9 @@ document.addEventListener('DOMContentLoaded', () => { let parentId = null; let getGroups = null; let page = null; + let sort = null; let pageParam = null; + let sortParam = null; let filterGroups = null; let filterGroupsParam = null; @@ -55,9 +57,14 @@ document.addEventListener('DOMContentLoaded', () => { filterGroups = filterGroupsParam; } - getGroups = this.service.getGroups(parentId, page, filterGroups); + sortParam = gl.utils.getParameterByName('sort'); + if (sortParam) { + sort = sortParam; + } + + getGroups = this.service.getGroups(parentId, page, filterGroups, sort); getGroups.then((response) => { - eventHub.$emit('updateGroups', response.json(), parentGroup); + this.updateGroups(response.json(), parentGroup); }) .catch(() => { // TODO: Handle error @@ -65,6 +72,17 @@ document.addEventListener('DOMContentLoaded', () => { return getGroups; }, + fetchPage(page, filterGroups, sort) { + this.service.getGroups(null, page, filterGroups, sort) + .then((response) => { + this.updateGroups(response.json()); + this.updatePagination(response.headers); + $.scrollTo(0); + }) + .catch(() => { + // TODO: Handle error + }); + }, toggleSubGroups(parentGroup = null) { if (!parentGroup.isOpen) { this.store.resetGroups(parentGroup); @@ -95,9 +113,18 @@ document.addEventListener('DOMContentLoaded', () => { const filter = document.querySelector('.js-groups-list-filter'); const holder = document.querySelector('.js-groups-list-holder'); - groupFilterList = new GroupFilterableList(form, filter, holder); + const opts = { + form, + filter, + holder, + filterEndpoint: el.dataset.endpoint, + pagePath: el.dataset.path, + }; + + groupFilterList = new GroupFilterableList(opts); groupFilterList.initSearch(); + eventHub.$on('fetchPage', this.fetchPage); eventHub.$on('toggleSubGroups', this.toggleSubGroups); eventHub.$on('leaveGroup', this.leaveGroup); eventHub.$on('updateGroups', this.updateGroups); @@ -106,7 +133,7 @@ document.addEventListener('DOMContentLoaded', () => { mounted() { this.fetchGroups() .then((response) => { - eventHub.$emit('updatePagination', response.headers); + this.updatePagination(response.headers); }) .catch(() => { // TODO: Handle error diff --git a/app/assets/javascripts/groups/services/groups_service.js b/app/assets/javascripts/groups/services/groups_service.js index b893a38a494..97e02fcb76d 100644 --- a/app/assets/javascripts/groups/services/groups_service.js +++ b/app/assets/javascripts/groups/services/groups_service.js @@ -8,7 +8,7 @@ export default class GroupsService { this.groups = Vue.resource(endpoint); } - getGroups(parentId, page, filterGroups) { + getGroups(parentId, page, filterGroups, sort) { const data = {}; if (parentId) { @@ -22,6 +22,10 @@ export default class GroupsService { if (filterGroups) { data.filter_groups = filterGroups; } + + if (sort) { + data.sort = sort; + } } return this.groups.get(data); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index a537267643e..2aca86189fd 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -167,8 +167,8 @@ if the name does not exist this function will return `null` otherwise it will return the value of the param key provided */ - w.gl.utils.getParameterByName = (name) => { - const url = window.location.href; + w.gl.utils.getParameterByName = (name, parseUrl) => { + const url = parseUrl || window.location.href; name = name.replace(/[[\]]/g, '\\$&'); const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); const results = regex.exec(url); diff --git a/app/views/dashboard/groups/_groups.html.haml b/app/views/dashboard/groups/_groups.html.haml index f6ac479d41a..b72b04479de 100644 --- a/app/views/dashboard/groups/_groups.html.haml +++ b/app/views/dashboard/groups/_groups.html.haml @@ -1,3 +1,3 @@ .js-groups-list-holder - #dashboard-group-app{ data: { endpoint: dashboard_groups_path(format: :json) } } + #dashboard-group-app{ data: { endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path } } %groups-component{ ':groups' => 'state.groups', ':page-info' => 'state.pageInfo' } |