From a0cd1f4cd0f34e6133160583dd6cd1323fa744a8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 15 Apr 2019 18:36:14 -0500 Subject: [PATCH] Add "most of" Buefy support for grid filters still a couple of details to wrap up yet, but this is most of it! --- tailbone/grids/core.py | 50 ++++- .../static/js/tailbone.buefy.gridfilters.js | 24 +++ .../static/themes/falafel/css/filters.css | 8 + tailbone/templates/grids/buefy.mako | 178 ++++++++++++++++-- tailbone/templates/master/index.mako | 6 +- tailbone/templates/themes/falafel/base.mako | 5 +- .../themes/falafel/grids/filters.mako | 123 +++++------- tailbone/views/master.py | 15 +- 8 files changed, 313 insertions(+), 96 deletions(-) create mode 100644 tailbone/static/js/tailbone.buefy.gridfilters.js create mode 100644 tailbone/static/themes/falafel/css/filters.css diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index bed22fe4..30708b54 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -384,6 +384,7 @@ class Grid(object): def render_grid(self, template='/grids/grid.mako', **kwargs): context = kwargs context['grid'] = self + context['request'] = self.request grid_class = '' if self.width == 'full': grid_class = 'full' @@ -912,10 +913,57 @@ class Grid(object): def render_buefy(self, template='/grids/buefy.mako', **kwargs): """ - Render the Buefy grid, including filters. + Render the Buefy grid, complete with filters. Note that this also + includes the context menu items and grid tools. """ + if 'grid_columns' not in kwargs: + kwargs['grid_columns'] = self.get_buefy_columns() + + if 'grid_data' not in kwargs: + kwargs['grid_data'] = self.get_buefy_data() + + if 'static_data' not in kwargs: + kwargs['static_data'] = self.has_static_data() + + if self.filterable and 'filters_data' not in kwargs: + kwargs['filters_data'] = self.get_filters_data() + + if self.filterable and 'filters_sequence' not in kwargs: + kwargs['filters_sequence'] = self.get_filters_sequence() + return self.render_complete(template=template, **kwargs) + def get_filters_sequence(self): + """ + Returns a list of filter keys (strings) in the sequence with which they + should be displayed in the UI. + """ + return list(self.filters) + + def get_filters_data(self): + """ + Returns a dict of current filters data, for use with Buefy grid view. + """ + data = {} + for filtr in self.filters.values(): + + valueless = [v for v in filtr.valueless_verbs + if v in filtr.verbs] + + data[filtr.key] = { + 'key': filtr.key, + 'label': filtr.label, + 'active': filtr.active, + 'visible': filtr.active, + 'verbs': filtr.verbs, + 'valueless_verbs': valueless, + 'verb_labels': filtr.verb_labels, + 'verb': filtr.verb or filtr.default_verb or filtr.verbs[0], + 'value': filtr.value, + } + + return data + def render_filters(self, template='/grids/filters.mako', **kwargs): """ Render the filters to a Unicode string, using the specified template. diff --git a/tailbone/static/js/tailbone.buefy.gridfilters.js b/tailbone/static/js/tailbone.buefy.gridfilters.js new file mode 100644 index 00000000..3a000f23 --- /dev/null +++ b/tailbone/static/js/tailbone.buefy.gridfilters.js @@ -0,0 +1,24 @@ + +const GridFilter = { + template: '#grid-filter-template', + props: { + filter: Object + }, + + methods: { + + changeVerb() { + // set focus to value input, "as quickly as we can" + this.$nextTick(function() { + this.focusValue() + }) + }, + + focusValue: function() { + this.$refs.valueInput.focus() + // this.$refs.valueInput.select() + } + } +} + +Vue.component('grid-filter', GridFilter) diff --git a/tailbone/static/themes/falafel/css/filters.css b/tailbone/static/themes/falafel/css/filters.css new file mode 100644 index 00000000..0681f29f --- /dev/null +++ b/tailbone/static/themes/falafel/css/filters.css @@ -0,0 +1,8 @@ + +/****************************** + * Grid Filters + ******************************/ + +.filters .filter { + margin-bottom: 0.5rem; +} diff --git a/tailbone/templates/grids/buefy.mako b/tailbone/templates/grids/buefy.mako index cd938063..314a4c09 100644 --- a/tailbone/templates/grids/buefy.mako +++ b/tailbone/templates/grids/buefy.mako @@ -1,6 +1,80 @@ ## -*- coding: utf-8; -*- -
+ + + +
+ +
+ +
+ % if grid.filterable: + ## TODO: stop using |n filter + ${grid.render_filters(allow_save_defaults=allow_save_defaults)|n} + % endif +
+ +
+ +
+ % if context_menu: +
    + ## TODO: stop using |n filter + ${context_menu|n} +
+ % endif +
+ +
+ % if tools: +
+ ## TODO: stop using |n filter + ${tools|n} +
+ % endif +
+ +
+ +
+ new Vue({ - el: '#buefy-table-app', + el: '#buefy-grid-app', data() { return { data: ${json.dumps(grid_data['data'])|n}, @@ -84,6 +158,7 @@ sortField: '${grid.sortkey}', sortOrder: '${grid.sortdir}', rowStatusMap: ${json.dumps(grid_data['row_status_map'])|n}, + % if grid.pageable: % if static_data: total: ${len(grid_data['data'])}, @@ -92,8 +167,14 @@ % endif perPage: ${grid.pagesize}, page: ${grid.page}, - firstItem: ${grid_data['first_item']}, - lastItem: ${grid_data['last_item']}, + firstItem: ${json.dumps(grid_data['first_item'])|n}, + lastItem: ${json.dumps(grid_data['last_item'])|n}, + % endif + + % if grid.filterable: + filters: ${json.dumps(filters_data)|n}, + filtersSequence: ${json.dumps(filters_sequence)|n}, + selectedFilter: null, % endif } }, @@ -103,15 +184,17 @@ return this.rowStatusMap[index] }, - loadAsyncData() { + loadAsyncData(params) { - const params = [ - 'partial=true', - `sortkey=${'$'}{this.sortField}`, - `sortdir=${'$'}{this.sortOrder}`, - `pagesize=${'$'}{this.perPage}`, - `page=${'$'}{this.page}` - ].join('&') + if (params === undefined) { + params = [ + 'partial=true', + `sortkey=${'$'}{this.sortField}`, + `sortdir=${'$'}{this.sortOrder}`, + `pagesize=${'$'}{this.perPage}`, + `page=${'$'}{this.page}` + ].join('&') + } this.loading = true this.$http.get(`${request.current_route_url(_query=None)}?${'$'}{params}`).then(({ data }) => { @@ -142,6 +225,77 @@ // TODO: i mean..right? would we ever not want that? this.page = 1 this.loadAsyncData() + }, + + resetView() { + this.loading = true + location.href = '?reset-to-default-filters=true' + }, + + addFilter(filter_key) { + + // reset dropdown so user again sees "Add Filter" placeholder + this.$nextTick(function() { + this.selectedFilter = null + }) + + // show corresponding grid filter + this.filters[filter_key].visible = true + this.filters[filter_key].active = true + + // track down the component + var gridFilter = null + for (var gf of this.$refs.gridFilters) { + if (gf.filter.key == filter_key) { + gridFilter = gf + break + } + } + + // tell component to focus the value field, ASAP + this.$nextTick(function() { + gridFilter.focusValue() + }) + + }, + + applyFilters(params) { + if (params === undefined) { + params = [] + } + + params.push('partial=true') + params.push('filter=true') + + for (var key in this.filters) { + var filter = this.filters[key] + if (filter.active) { + params.push(key + '=' + encodeURIComponent(filter.value)) + params.push(key + '.verb=' + encodeURIComponent(filter.verb)) + } else { + filter.visible = false + } + } + + this.loadAsyncData(params.join('&')) + }, + + clearFilters() { + + // explicitly deactivate all filters + for (var key in this.filters) { + this.filters[key].active = false + } + + // then just "apply" as normal + this.applyFilters() + }, + + saveDefaults() { + + // apply current filters as normal, but add special directive + const params = ['save-current-filters-as-defaults=true'] + this.applyFilters(params) } } diff --git a/tailbone/templates/master/index.mako b/tailbone/templates/master/index.mako index 9a502a67..63c21d66 100644 --- a/tailbone/templates/master/index.mako +++ b/tailbone/templates/master/index.mako @@ -14,6 +14,9 @@ <%def name="extra_javascript()"> ${parent.extra_javascript()} + % if use_buefy: + ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.gridfilters.js') + '?ver={}'.format(tailbone.__version__))} + % endif @@ -331,6 +331,7 @@ ${h.stylesheet_link(request.static_url('tailbone:static/css/grids.css') + '?ver={}'.format(tailbone.__version__))} ${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/grids.rowstatus.css') + '?ver={}'.format(tailbone.__version__))} ## ${h.stylesheet_link(request.static_url('tailbone:static/css/filters.css') + '?ver={}'.format(tailbone.__version__))} + ${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/filters.css') + '?ver={}'.format(tailbone.__version__))} ${h.stylesheet_link(request.static_url('tailbone:static/themes/bobcat/css/forms.css') + '?ver={}'.format(tailbone.__version__))} ${h.stylesheet_link(request.static_url('tailbone:static/css/diffs.css') + '?ver={}'.format(tailbone.__version__))} diff --git a/tailbone/templates/themes/falafel/grids/filters.mako b/tailbone/templates/themes/falafel/grids/filters.mako index 38e0b71a..e686d166 100644 --- a/tailbone/templates/themes/falafel/grids/filters.mako +++ b/tailbone/templates/themes/falafel/grids/filters.mako @@ -1,76 +1,57 @@ ## -*- coding: utf-8; -*- -
- ${h.form(form.action_url, method='get')} - ${h.hidden('reset-to-default-filters', value='false')} - ${h.hidden('save-current-filters-as-defaults', value='false')} +
-
- Filters - % for filtr in form.iter_filters(): - - % endfor -
+ + -
-
-
- ## - - - - - Apply Filters - -
-
-
- -
-
-
- ## - - - - - Default View - -
-
- ## - - - - - No Filters - -
- % if allow_save_defaults and request.user: -
- ## - - - - - Save Defaults - -
- % endif -
-
+ - ${h.end_form()} -
+ + Apply Filters + + + + + + + + Default View + + + + No Filters + + + % if allow_save_defaults and request.user: + + Save Defaults + + % endif + + + + diff --git a/tailbone/views/master.py b/tailbone/views/master.py index f7e2203f..c7ae41d2 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -253,6 +253,9 @@ class MasterView(View): labels.update(cls.row_labels) return labels + def get_use_buefy(self): + return self.use_buefy and self.rattail_config.getbool('tailbone', 'grids.use_buefy') + ############################## # Available Views ############################## @@ -267,7 +270,7 @@ class MasterView(View): """ self.listing = True grid = self.make_grid() - use_buefy = self.use_buefy and self.rattail_config.getbool('tailbone', 'grids.use_buefy') + use_buefy = self.get_use_buefy() # If user just refreshed the page with a reset instruction, issue a # redirect in order to clear out the query string. @@ -293,12 +296,6 @@ class MasterView(View): 'grid': grid, 'use_buefy': use_buefy, } - - if use_buefy: - context['grid_columns'] = grid.get_buefy_columns() - context['grid_data'] = grid.get_buefy_data() - context['static_data'] = grid.has_static_data() - return self.render_to_response('index', context) def make_grid(self, factory=None, key=None, data=None, columns=None, **kwargs): @@ -2253,7 +2250,7 @@ class MasterView(View): """ actions = [] prefix = self.get_permission_prefix() - use_buefy = self.use_buefy and self.rattail_config.getbool('tailbone', 'grids.use_buefy') + use_buefy = self.get_use_buefy() if self.viewable and self.request.has_perm('{}.view'.format(prefix)): url = self.get_view_index_url if self.use_index_links else None icon = 'eye' if use_buefy else 'zoomin' @@ -2270,7 +2267,7 @@ class MasterView(View): """ actions = [] prefix = self.get_permission_prefix() - use_buefy = self.use_buefy and self.rattail_config.getbool('tailbone', 'grids.use_buefy') + use_buefy = self.get_use_buefy() if self.editable and self.request.has_perm('{}.edit'.format(prefix)): icon = 'edit' if use_buefy else 'pencil' actions.append(self.make_action('edit', icon=icon, url=self.default_edit_url))