Make Buefy grids use proper Vue.js component structure

at least, better than before...this lets each page have the final say about the
app logic etc.
This commit is contained in:
Lance Edgar 2019-05-23 12:10:11 -05:00
parent 6c3722737d
commit 7b1947914e
4 changed files with 340 additions and 317 deletions

View file

@ -0,0 +1,168 @@
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)
let TailboneGrid = {
template: '#tailbone-grid-template',
methods: {
getRowClass(row, index) {
return this.rowStatusMap[index]
},
loadAsyncData(params) {
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(`?${params}`).then(({ data }) => {
this.data = data.data
this.rowStatusMap = data.row_status_map
this.total = data.total_items
this.firstItem = data.first_item
this.lastItem = data.last_item
this.loading = false
})
.catch((error) => {
this.data = []
this.total = 0
this.loading = false
throw error
})
},
onPageChange(page) {
this.page = page
this.loadAsyncData()
},
onSort(field, order) {
this.sortField = field
this.sortOrder = order
// always reset to first page when changing sort options
// 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)
},
deleteResults(event) {
// submit form if user confirms
// TODO: how/where to get/show "plural model title" here?
// if (confirm("You are about to delete " + this.total + " ${grid.model_title_plural}.\n\nAre you sure?")) {
if (confirm("You are about to delete " + this.total + " objects.\n\nAre you sure?")) {
event.target.form.submit()
}
},
checkedRowUUIDs() {
var uuids = [];
for (var row of this.$data.checkedRows) {
uuids.push(row.uuid)
}
return uuids
}
}
}

View file

@ -1,24 +0,0 @@
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)

View file

@ -60,8 +60,8 @@
</script>
<div id="buefy-grid-app">
<script type="text/x-template" id="tailbone-grid-template">
<div>
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5em;">
@ -101,29 +101,28 @@
<b-table
:data="data"
:columns="columns"
## :columns="columns"
:loading="loading"
:row-class="getRowClass"
% if grid.checkboxes:
checkable
:checkable="checkable"
:checked-rows.sync="checkedRows"
## TODO: definitely will be wanting this...
## :is-row-checkable=""
% endif
:default-sort="[sortField, sortOrder]"
backend-sorting
@sort="onSort"
% if grid.pageable:
paginated
## % if grid.pageable:
## paginated
:paginated="paginated"
:per-page="perPage"
:current-page="page"
:current-page="currentPage"
backend-pagination
:total="total"
@page-change="onPageChange"
% endif
## % endif
## TODO: should let grid (or master view) decide how to set these?
icon-pack="fas"
@ -191,180 +190,40 @@
</b-table>
</div>
</script>
<script type="text/javascript">
new Vue({
el: '#buefy-grid-app',
data() {
return {
data: ${json.dumps(grid_data['data'])|n},
let TailboneGridData = {
loading: false,
rowStatusMap: ${json.dumps(grid_data['row_status_map'])|n},
## TODO: should be dumping json from server here
checkedRows: [],
% if grid.sortable:
sortField: '${grid.sortkey}',
sortOrder: '${grid.sortdir}',
% endif
% if grid.pageable:
% if static_data:
total: ${len(grid_data['data'])},
% else:
total: ${grid_data['total_items']},
% endif
perPage: ${grid.pagesize},
page: ${grid.page},
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
data: ${json.dumps(grid_data['data'])|n},
rowStatusMap: ${json.dumps(grid_data['row_status_map'])|n},
checkable: ${json.dumps(grid.checkboxes)|n},
## TODO: should be dumping json from server here?
## checkedRows: [],
paginated: ${json.dumps(grid.pageable)|n},
total: ${len(grid_data['data']) if static_data else grid_data['total_items']},
perPage: ${json.dumps(grid.pagesize if grid.pageable else None)|n},
currentPage: ${json.dumps(grid.page if grid.pageable else None)|n},
firstItem: ${json.dumps(grid_data['first_item'] if grid.pageable else None)|n},
lastItem: ${json.dumps(grid_data['last_item'] if grid.pageable else None)|n},
sortField: ${json.dumps(grid.sortkey if grid.sortable else None)|n},
sortOrder: ${json.dumps(grid.sortdir if grid.sortable else None)|n},
## filterable: ${json.dumps(grid.filterable)|n},
filters: ${json.dumps(filters_data if grid.filterable else None)|n},
filtersSequence: ${json.dumps(filters_sequence if grid.filterable else None)|n},
selectedFilter: null,
}
},
methods: {
getRowClass(row, index) {
return this.rowStatusMap[index]
},
loadAsyncData(params) {
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 }) => {
this.data = data.data
this.rowStatusMap = data.row_status_map
this.total = data.total_items
this.firstItem = data.first_item
this.lastItem = data.last_item
this.loading = false
})
.catch((error) => {
this.data = []
this.total = 0
this.loading = false
throw error
})
},
onPageChange(page) {
this.page = page
this.loadAsyncData()
},
onSort(field, order) {
this.sortField = field
this.sortOrder = order
// always reset to first page when changing sort options
// 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)
},
deleteResults(event) {
// submit form if user confirms
if (confirm("You are about to delete " + this.total + " ${grid.model_title_plural}.\n\nAre you sure?")) {
event.target.form.submit()
}
},
checkedRowUUIDs() {
var uuids = [];
for (var row of this.$data.checkedRows) {
uuids.push(row.uuid)
}
return uuids
}
}
});
</script>
<div id="tailbone-grid-app">
<tailbone-grid></tailbone-grid>
</div>

View file

@ -15,7 +15,7 @@
<%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__))}
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.grid.js') + '?ver={}'.format(tailbone.__version__))}
% endif
<script type="text/javascript">
$(function() {
@ -200,10 +200,30 @@
</%def>
<%def name="modify_tailbone_grid()">
## NOTE: if you override this, must use <script> tags
</%def>
<%def name="make_tailbone_grid_app()">
${self.modify_tailbone_grid()}
<script type="text/javascript">
TailboneGrid.data = function() { return TailboneGridData }
Vue.component('tailbone-grid', TailboneGrid)
new Vue({
el: '#tailbone-grid-app'
});
</script>
</%def>
% if use_buefy:
## TODO: stop using |n filter
${grid.render_buefy(tools=capture(self.grid_tools).strip(), context_menu=capture(self.context_menu_items).strip())|n}
${self.make_tailbone_grid_app()}
% else:
## no buefy, so do the traditional thing