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
+
+
+
+
+
+
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__))}
%def>
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')}
+
+
+ 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))