Improve pagination when searching or filtering

[ci skip]
This commit is contained in:
Alfredo Sumaran 2017-06-06 00:06:08 -05:00
parent ea531e1eff
commit 323a326c73
7 changed files with 130 additions and 48 deletions

View File

@ -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);
}
}

View File

@ -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);
},
},
};

View File

@ -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'),

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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' }