summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlfredo Sumaran <alfredo@gitlab.com>2017-06-06 00:06:08 -0500
committerAlfredo Sumaran <alfredo@gitlab.com>2017-06-06 04:31:40 -0500
commit323a326c73f4aabf37bf79f8e42350c128983c2d (patch)
treefb21b276fac004c817871c68af5a349bc8ed012f
parentea531e1effa51bcec84e50a69901e6eec7c789c1 (diff)
downloadgitlab-ce-323a326c73f4aabf37bf79f8e42350c128983c2d.tar.gz
Improve pagination when searching or filtering
[ci skip]
-rw-r--r--app/assets/javascripts/filterable_list.js62
-rw-r--r--app/assets/javascripts/groups/components/groups.vue12
-rw-r--r--app/assets/javascripts/groups/groups_filterable_list.js57
-rw-r--r--app/assets/javascripts/groups/index.js35
-rw-r--r--app/assets/javascripts/groups/services/groups_service.js6
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js4
-rw-r--r--app/views/dashboard/groups/_groups.html.haml2
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' }