Add front-end support for multi-column grid sorting

user must ctrl-click column header to engage multi-sort
This commit is contained in:
Lance Edgar 2023-10-08 16:38:13 -05:00
parent 6d7754cf2a
commit edb5393cdc
2 changed files with 128 additions and 30 deletions

View file

@ -830,10 +830,10 @@ class Grid(object):
if self.sortable: if self.sortable:
self.active_sorters = [] self.active_sorters = []
for i in range(1, settings['sorters.length'] + 1): for i in range(1, settings['sorters.length'] + 1):
self.active_sorters.append(( self.active_sorters.append({
settings[f'sorters.{i}.key'], 'field': settings[f'sorters.{i}.key'],
settings[f'sorters.{i}.dir'], 'order': settings[f'sorters.{i}.dir'],
)) })
if self.pageable: if self.pageable:
self.pagesize = settings['pagesize'] self.pagesize = settings['pagesize']
self.page = settings['page'] self.page = settings['page']
@ -1229,9 +1229,14 @@ class Grid(object):
if not self.active_sorters: if not self.active_sorters:
return data return data
# TODO: is there a better way to check for SA sorting?
if self.model_class:
# convert sort settings into a 'sortspec' for use with sa-filters # convert sort settings into a 'sortspec' for use with sa-filters
full_spec = [] full_spec = []
for sortkey, sortdir in self.active_sorters: for sorter in self.active_sorters:
sortkey = sorter['field']
sortdir = sorter['order']
sortfunc = self.sorters.get(sortkey) sortfunc = self.sorters.get(sortkey)
if sortfunc: if sortfunc:
spec = { spec = {
@ -1240,7 +1245,6 @@ class Grid(object):
'field': sortfunc._column.name, 'field': sortfunc._column.name,
'direction': sortdir or 'asc', 'direction': sortdir or 'asc',
} }
# spec.sortkey = sortkey
full_spec.append(spec) full_spec.append(spec)
# apply joins needed for this sort spec # apply joins needed for this sort spec
@ -1252,6 +1256,26 @@ class Grid(object):
return apply_sort(data, full_spec) return apply_sort(data, full_spec)
else:
# not a SQLAlchemy grid, custom sorter
assert len(self.active_sorters) < 2
sortkey = self.active_sorters[0]['field']
sortdir = self.active_sorters[0]['order'] or 'asc'
# Cannot sort unless we have a sort function.
sortfunc = self.sorters.get(sortkey)
if not sortfunc:
return data
# apply joins needed for this sorter
if sortkey in self.joiners and sortkey not in self.joined:
data = self.joiners[sortkey](data)
self.joined.add(sortkey)
return sortfunc(data, sortdir)
def paginate_data(self, data): def paginate_data(self, data):
""" """
Paginate the given data set according to current settings, and return Paginate the given data set according to current settings, and return

View file

@ -202,9 +202,25 @@
% endif % endif
% if grid.sortable: % if grid.sortable:
:default-sort="sortingPriority[0]"
backend-sorting backend-sorting
@sort="onSort" @sort="onSort"
@sorting-priority-removed="sortingPriorityRemoved"
## TODO: there is a bug (?) which prevents the arrow from
## displaying for simple default single-column sort. so to
## work around that, we *disable* multi-sort until the
## component is mounted. seems to work for now..see also
## https://github.com/buefy/buefy/issues/2584
:sort-multiple="allowMultiSort"
## nb. specify default sort only if single-column
:default-sort="backendSorters.length == 1 ? [backendSorters[0].field, backendSorters[0].order] : null"
## nb. otherwise there may be default multi-column sort
:sort-multiple-data="sortingPriority"
## user must ctrl-click column header to do multi-sort
sort-multiple-key="ctrlKey"
% endif % endif
% if grid.click_handlers: % if grid.click_handlers:
@ -353,7 +369,25 @@
lastItem: ${json.dumps(grid_data['last_item'] if grid.pageable else None)|n}, lastItem: ${json.dumps(grid_data['last_item'] if grid.pageable else None)|n},
% if grid.sortable: % if grid.sortable:
## TODO: there is a bug (?) which prevents the arrow from
## displaying for simple default single-column sort. so to
## work around that, we *disable* multi-sort until the
## component is mounted. seems to work for now..see also
## https://github.com/buefy/buefy/issues/2584
allowMultiSort: false,
## nb. this contains all truly active sorters
backendSorters: ${json.dumps(grid.active_sorters)|n},
## nb. whereas this will only contain multi-column sorters,
## but will be *empty* for single-column sorting
% if len(grid.active_sorters) > 1:
sortingPriority: ${json.dumps(grid.active_sorters)|n}, sortingPriority: ${json.dumps(grid.active_sorters)|n},
% else:
sortingPriority: [],
% endif
% endif % endif
## filterable: ${json.dumps(grid.filterable)|n}, ## filterable: ${json.dumps(grid.filterable)|n},
@ -395,6 +429,15 @@
}, },
}, },
mounted() {
## TODO: there is a bug (?) which prevents the arrow from
## displaying for simple default single-column sort. so to
## work around that, we *disable* multi-sort until the
## component is mounted. seems to work for now..see also
## https://github.com/buefy/buefy/issues/2584
this.allowMultiSort = true
},
methods: { methods: {
% if grid.click_handlers: % if grid.click_handlers:
@ -455,9 +498,9 @@
getBasicParams() { getBasicParams() {
let params = {} let params = {}
% if grid.sortable: % if grid.sortable:
for (let i = 1; i <= this.sortingPriority.length; i++) { for (let i = 1; i <= this.backendSorters.length; i++) {
params['sort'+i+'key'] = this.sortingPriority[i-1][0] params['sort'+i+'key'] = this.backendSorters[i-1].field
params['sort'+i+'dir'] = this.sortingPriority[i-1][1] params['sort'+i+'dir'] = this.backendSorters[i-1].order
} }
% endif % endif
% if grid.pageable: % if grid.pageable:
@ -537,14 +580,45 @@
this.loadAsyncData() this.loadAsyncData()
}, },
onSort(field, order) { onSort(field, order, event) {
this.sortingPriority = [[field, order]]
if (event.ctrlKey) {
// engage or enhance multi-column sorting
let sorter = this.backendSorters.filter(i => i.field === field)[0]
if (sorter) {
sorter.order = sorter.order === 'desc' ? 'asc' : 'desc'
} else {
this.backendSorters.push({field, order})
}
this.sortingPriority = this.backendSorters
} else {
// sort by single column only
this.backendSorters = [{field, order}]
this.sortingPriority = []
}
// always reset to first page when changing sort options // always reset to first page when changing sort options
// TODO: i mean..right? would we ever not want that? // TODO: i mean..right? would we ever not want that?
this.currentPage = 1 this.currentPage = 1
this.loadAsyncData() this.loadAsyncData()
}, },
sortingPriorityRemoved(field) {
// prune field from active sorters
this.backendSorters = this.backendSorters.filter(
(sorter) => sorter.field !== field)
// nb. must keep active sorter list "as-is" even if
// there is only one sorter; buefy seems to expect it
this.sortingPriority = this.backendSorters
this.loadAsyncData()
},
resetView() { resetView() {
this.loading = true this.loading = true