Let each grid component have a custom name, if needed

This commit is contained in:
Lance Edgar 2020-05-20 19:19:06 -05:00
parent 8683e2a4c2
commit a8a79ee326
5 changed files with 213 additions and 202 deletions

View file

@ -75,7 +75,7 @@ class Grid(object):
pageable=False, default_pagesize=20, default_page=1, pageable=False, default_pagesize=20, default_page=1,
checkboxes=False, checked=None, check_handler=None, check_all_handler=None, checkboxes=False, checked=None, check_handler=None, check_all_handler=None,
main_actions=[], more_actions=[], delete_speedbump=False, main_actions=[], more_actions=[], delete_speedbump=False,
ajax_data_url=None, ajax_data_url=None, component='tailbone-grid',
**kwargs): **kwargs):
self.key = key self.key = key
@ -139,8 +139,14 @@ class Grid(object):
else: else:
self.ajax_data_url = '' self.ajax_data_url = ''
self.component = component
self._whgrid_kwargs = kwargs self._whgrid_kwargs = kwargs
@property
def component_studly(self):
words = self.component.split('-')
return ''.join([word.capitalize() for word in words])
def make_columns(self): def make_columns(self):
""" """
Return a default list of columns, based on :attr:`model_class`. Return a default list of columns, based on :attr:`model_class`.
@ -1237,8 +1243,9 @@ class Grid(object):
results['checked_rows'] = checked results['checked_rows'] = checked
# TODO: this seems a bit hacky, but is required for now to # TODO: this seems a bit hacky, but is required for now to
# initialize things on the client side... # initialize things on the client side...
var = '{}CurrentData'.format(self.component_studly)
results['checked_rows_code'] = '[{}]'.format( results['checked_rows_code'] = '[{}]'.format(
', '.join(['TailboneGridCurrentData[{}]'.format(i) for i in checked])) ', '.join(['{}[{}]'.format(var, i) for i in checked]))
if self.pageable and self.pager is not None: if self.pageable and self.pager is not None:
results['total_items'] = self.pager.item_count results['total_items'] = self.pager.item_count

View file

@ -88,176 +88,3 @@ const GridFilter = {
} }
Vue.component('grid-filter', GridFilter) Vue.component('grid-filter', GridFilter)
let TailboneGrid = {
template: '#tailbone-grid-template',
props: {
csrftoken: String,
},
computed: {
// note, can use this with v-model for hidden 'uuids' fields
selected_uuids: function() {
return this.checkedRowUUIDs().join(',')
},
},
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(`${this.ajaxDataUrl}?${params}`).then(({ data }) => {
TailboneGridCurrentData = data.data
this.data = TailboneGridCurrentData
this.rowStatusMap = data.row_status_map
this.total = data.total_items
this.firstItem = data.first_item
this.lastItem = data.last_item
this.loading = false
this.checkedRows = this.locateCheckedRows(data.checked_rows)
})
.catch((error) => {
this.data = []
this.total = 0
this.loading = false
throw error
})
},
locateCheckedRows(checked) {
let rows = []
if (checked) {
for (let i = 0; i < this.data.length; i++) {
if (checked.includes(i)) {
rows.push(this.data[i])
}
}
}
return rows
},
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)
},
deleteObject(event) {
// we let parent component/app deal with this, in whatever way makes sense...
// TODO: should we ever provide anything besides the URL for this?
this.$emit('deleteActionClicked', event.target.href)
},
checkedRowUUIDs() {
let uuids = []
for (let row of this.$data.checkedRows) {
uuids.push(row.uuid)
}
return uuids
},
allRowUUIDs() {
let uuids = []
for (let row of this.data) {
uuids.push(row.uuid)
}
return uuids
},
}
}

View file

@ -85,7 +85,7 @@
</script> </script>
<script type="text/x-template" id="tailbone-grid-template"> <script type="text/x-template" id="${grid.component}-template">
<div> <div>
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5em;"> <div style="display: flex; justify-content: space-between; margin-bottom: 0.5em;">
@ -236,14 +236,14 @@
<script type="text/javascript"> <script type="text/javascript">
let TailboneGridCurrentData = ${json.dumps(grid_data['data'])|n} let ${grid.component_studly}CurrentData = ${json.dumps(grid_data['data'])|n}
let TailboneGridData = { let ${grid.component_studly}Data = {
loading: false, loading: false,
selectedFilter: null, selectedFilter: null,
ajaxDataUrl: ${json.dumps(grid.ajax_data_url)|n}, ajaxDataUrl: ${json.dumps(grid.ajax_data_url)|n},
data: TailboneGridCurrentData, data: ${grid.component_studly}CurrentData,
rowStatusMap: ${json.dumps(grid_data['row_status_map'])|n}, rowStatusMap: ${json.dumps(grid_data['row_status_map'])|n},
checkable: ${json.dumps(grid.checkboxes)|n}, checkable: ${json.dumps(grid.checkboxes)|n},
@ -267,4 +267,179 @@
selectedFilter: null, selectedFilter: null,
} }
let ${grid.component_studly} = {
template: '#${grid.component}-template',
props: {
csrftoken: String,
},
computed: {
// note, can use this with v-model for hidden 'uuids' fields
selected_uuids: function() {
return this.checkedRowUUIDs().join(',')
},
},
methods: {
getRowClass(row, index) {
return this.rowStatusMap[index]
},
loadAsyncData(params, callback) {
if (params === undefined || params === null) {
params = [
'partial=true',
`sortkey=${'$'}{this.sortField}`,
`sortdir=${'$'}{this.sortOrder}`,
`pagesize=${'$'}{this.perPage}`,
`page=${'$'}{this.page}`
].join('&')
}
this.loading = true
this.$http.get(`${'$'}{this.ajaxDataUrl}?${'$'}{params}`).then(({ data }) => {
${grid.component_studly}CurrentData = data.data
this.data = ${grid.component_studly}CurrentData
this.rowStatusMap = data.row_status_map
this.total = data.total_items
this.firstItem = data.first_item
this.lastItem = data.last_item
this.loading = false
this.checkedRows = this.locateCheckedRows(data.checked_rows)
if (callback) {
callback()
}
})
.catch((error) => {
this.data = []
this.total = 0
this.loading = false
throw error
})
},
locateCheckedRows(checked) {
let rows = []
if (checked) {
for (let i = 0; i < this.data.length; i++) {
if (checked.includes(i)) {
rows.push(this.data[i])
}
}
}
return rows
},
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)
},
deleteObject(event) {
// we let parent component/app deal with this, in whatever way makes sense...
// TODO: should we ever provide anything besides the URL for this?
this.$emit('deleteActionClicked', event.target.href)
},
checkedRowUUIDs() {
let uuids = []
for (let row of this.$data.checkedRows) {
uuids.push(row.uuid)
}
return uuids
},
allRowUUIDs() {
let uuids = []
for (let row of this.data) {
uuids.push(row.uuid)
}
return uuids
},
}
}
</script> </script>

View file

@ -256,12 +256,12 @@
</%def> </%def>
<%def name="page_content()"> <%def name="page_content()">
<tailbone-grid :csrftoken="csrftoken" <${grid.component} :csrftoken="csrftoken"
% if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple': % if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple':
@deleteActionClicked="deleteObject" @deleteActionClicked="deleteObject"
% endif % endif
> >
</tailbone-grid> </${grid.component}>
% if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple': % if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple':
${h.form('#', ref='deleteObjectForm')} ${h.form('#', ref='deleteObjectForm')}
${h.csrf_token(request)} ${h.csrf_token(request)}
@ -273,9 +273,9 @@
${parent.make_this_page_component()} ${parent.make_this_page_component()}
<script type="text/javascript"> <script type="text/javascript">
TailboneGrid.data = function() { return TailboneGridData } ${grid.component_studly}.data = function() { return ${grid.component_studly}Data }
Vue.component('tailbone-grid', TailboneGrid) Vue.component('${grid.component}', ${grid.component_studly})
</script> </script>
</%def> </%def>
@ -309,10 +309,10 @@
## enable / disable selected objects ## enable / disable selected objects
% if master.supports_set_enabled_toggle and master.has_perm('enable_disable_set'): % if master.supports_set_enabled_toggle and master.has_perm('enable_disable_set'):
TailboneGridData.enableSelectedSubmitting = false ${grid.component_studly}Data.enableSelectedSubmitting = false
TailboneGridData.enableSelectedText = "Enable Selected" ${grid.component_studly}Data.enableSelectedText = "Enable Selected"
TailboneGrid.computed.enableSelectedDisabled = function() { ${grid.component_studly}.computed.enableSelectedDisabled = function() {
if (this.enableSelectedSubmitting) { if (this.enableSelectedSubmitting) {
return true return true
} }
@ -322,7 +322,7 @@
return false return false
} }
TailboneGrid.methods.enableSelectedSubmit = function() { ${grid.component_studly}.methods.enableSelectedSubmit = function() {
let uuids = this.checkedRowUUIDs() let uuids = this.checkedRowUUIDs()
if (!uuids.length) { if (!uuids.length) {
alert("You must first select one or more objects to disable.") alert("You must first select one or more objects to disable.")
@ -337,10 +337,10 @@
this.$refs.enable_selected_form.submit() this.$refs.enable_selected_form.submit()
} }
TailboneGridData.disableSelectedSubmitting = false ${grid.component_studly}Data.disableSelectedSubmitting = false
TailboneGridData.disableSelectedText = "Disable Selected" ${grid.component_studly}Data.disableSelectedText = "Disable Selected"
TailboneGrid.computed.disableSelectedDisabled = function() { ${grid.component_studly}.computed.disableSelectedDisabled = function() {
if (this.disableSelectedSubmitting) { if (this.disableSelectedSubmitting) {
return true return true
} }
@ -350,7 +350,7 @@
return false return false
} }
TailboneGrid.methods.disableSelectedSubmit = function() { ${grid.component_studly}.methods.disableSelectedSubmit = function() {
let uuids = this.checkedRowUUIDs() let uuids = this.checkedRowUUIDs()
if (!uuids.length) { if (!uuids.length) {
alert("You must first select one or more objects to disable.") alert("You must first select one or more objects to disable.")
@ -370,10 +370,10 @@
## delete selected objects ## delete selected objects
% if master.set_deletable and master.has_perm('delete_set'): % if master.set_deletable and master.has_perm('delete_set'):
TailboneGridData.deleteSelectedSubmitting = false ${grid.component_studly}Data.deleteSelectedSubmitting = false
TailboneGridData.deleteSelectedText = "Delete Selected" ${grid.component_studly}Data.deleteSelectedText = "Delete Selected"
TailboneGrid.computed.deleteSelectedDisabled = function() { ${grid.component_studly}.computed.deleteSelectedDisabled = function() {
if (this.deleteSelectedSubmitting) { if (this.deleteSelectedSubmitting) {
return true return true
} }
@ -383,7 +383,7 @@
return false return false
} }
TailboneGrid.methods.deleteSelectedSubmit = function() { ${grid.component_studly}.methods.deleteSelectedSubmit = function() {
let uuids = this.checkedRowUUIDs() let uuids = this.checkedRowUUIDs()
if (!uuids.length) { if (!uuids.length) {
alert("You must first select one or more objects to disable.") alert("You must first select one or more objects to disable.")
@ -401,10 +401,10 @@
% if master.bulk_deletable and master.has_perm('bulk_delete'): % if master.bulk_deletable and master.has_perm('bulk_delete'):
TailboneGridData.deleteResultsSubmitting = false ${grid.component_studly}Data.deleteResultsSubmitting = false
TailboneGridData.deleteResultsText = "Delete Results" ${grid.component_studly}Data.deleteResultsText = "Delete Results"
TailboneGrid.computed.deleteResultsDisabled = function() { ${grid.component_studly}.computed.deleteResultsDisabled = function() {
if (this.deleteResultsSubmitting) { if (this.deleteResultsSubmitting) {
return true return true
} }
@ -414,7 +414,7 @@
return false return false
} }
TailboneGrid.methods.deleteResultsSubmit = function() { ${grid.component_studly}.methods.deleteResultsSubmit = function() {
// TODO: show "plural model title" here? // TODO: show "plural model title" here?
if (!confirm("You are about to delete " + this.total.toLocaleString('en') + " objects.\n\nAre you sure?")) { if (!confirm("You are about to delete " + this.total.toLocaleString('en') + " objects.\n\nAre you sure?")) {
return return
@ -429,10 +429,10 @@
% if master.mergeable and master.has_perm('merge'): % if master.mergeable and master.has_perm('merge'):
TailboneGridData.mergeFormButtonText = "Merge 2 ${model_title_plural}" ${grid.component_studly}Data.mergeFormButtonText = "Merge 2 ${model_title_plural}"
TailboneGridData.mergeFormSubmitting = false ${grid.component_studly}Data.mergeFormSubmitting = false
TailboneGrid.methods.submitMergeForm = function() { ${grid.component_studly}.methods.submitMergeForm = function() {
this.mergeFormSubmitting = true this.mergeFormSubmitting = true
this.mergeFormButtonText = "Working, please wait..." this.mergeFormButtonText = "Working, please wait..."
} }

View file

@ -102,6 +102,7 @@
</%def> </%def>
<%def name="make_this_page_component()"> <%def name="make_this_page_component()">
% if master.has_rows:
<script type="text/javascript"> <script type="text/javascript">
TailboneGrid.data = function() { return TailboneGridData } TailboneGrid.data = function() { return TailboneGridData }
@ -109,6 +110,7 @@
Vue.component('tailbone-grid', TailboneGrid) Vue.component('tailbone-grid', TailboneGrid)
</script> </script>
% endif
${parent.make_this_page_component()} ${parent.make_this_page_component()}
</%def> </%def>