
so far only "simple" pagination is supported by wuttaweb, so basically the main feature flag, page size, current page. in this scenario *all* data is written to client-side JSON and Buefy handles the actual pagination. backend pagination coming soon for wuttaweb but for now tailbone still handles all that.
787 lines
26 KiB
Mako
787 lines
26 KiB
Mako
## -*- coding: utf-8; -*-
|
|
|
|
<% request.register_component(grid.vue_tagname, grid.vue_component) %>
|
|
|
|
<script type="text/x-template" id="${grid.vue_tagname}-template">
|
|
<div>
|
|
|
|
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5em;">
|
|
|
|
<div style="display: flex; flex-direction: column; justify-content: end;">
|
|
<div class="filters">
|
|
% if getattr(grid, 'filterable', False):
|
|
## TODO: stop using |n filter
|
|
${grid.render_filters(allow_save_defaults=allow_save_defaults)|n}
|
|
% endif
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: flex; flex-direction: column; justify-content: space-between;">
|
|
|
|
<div class="context-menu">
|
|
% if context_menu:
|
|
<ul id="context-menu">
|
|
## TODO: stop using |n filter
|
|
${context_menu|n}
|
|
</ul>
|
|
% endif
|
|
</div>
|
|
|
|
<div class="grid-tools-wrapper">
|
|
% if tools:
|
|
<div class="grid-tools">
|
|
## TODO: stop using |n filter
|
|
${tools|n}
|
|
</div>
|
|
% endif
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<${b}-table
|
|
:data="visibleData"
|
|
:loading="loading"
|
|
:row-class="getRowClass"
|
|
% if request.use_oruga:
|
|
tr-checked-class="is-checked"
|
|
% endif
|
|
|
|
% if request.rattail_config.getbool('tailbone', 'sticky_headers'):
|
|
sticky-header
|
|
height="600px"
|
|
% endif
|
|
|
|
:checkable="checkable"
|
|
|
|
% if getattr(grid, 'checkboxes', False):
|
|
% if request.use_oruga:
|
|
v-model:checked-rows="checkedRows"
|
|
% else:
|
|
:checked-rows.sync="checkedRows"
|
|
% endif
|
|
% if grid.clicking_row_checks_box:
|
|
@click="rowClick"
|
|
% endif
|
|
% endif
|
|
|
|
% if getattr(grid, 'check_handler', None):
|
|
@check="${grid.check_handler}"
|
|
% endif
|
|
% if getattr(grid, 'check_all_handler', None):
|
|
@check-all="${grid.check_all_handler}"
|
|
% endif
|
|
|
|
% if hasattr(grid, 'checkable'):
|
|
% if isinstance(grid.checkable, str):
|
|
:is-row-checkable="${grid.row_checkable}"
|
|
% elif grid.checkable:
|
|
:is-row-checkable="row => row._checkable"
|
|
% endif
|
|
% endif
|
|
|
|
% if getattr(grid, 'sortable', False):
|
|
backend-sorting
|
|
@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
|
|
|
|
% if getattr(grid, 'click_handlers', None):
|
|
@cellclick="cellClick"
|
|
% endif
|
|
|
|
% if grid.paginated:
|
|
:paginated="paginated"
|
|
:per-page="perPage"
|
|
:current-page="currentPage"
|
|
backend-pagination
|
|
:total="total"
|
|
@page-change="onPageChange"
|
|
% endif
|
|
|
|
## TODO: should let grid (or master view) decide how to set these?
|
|
icon-pack="fas"
|
|
## note that :striped="true" was interfering with row status (e.g. warning) styles
|
|
:striped="false"
|
|
:hoverable="true"
|
|
:narrowed="true">
|
|
|
|
% for column in grid.get_vue_columns():
|
|
<${b}-table-column field="${column['field']}"
|
|
label="${column['label']}"
|
|
v-slot="props"
|
|
:sortable="${json.dumps(column.get('sortable', False))}"
|
|
% if hasattr(grid, 'is_searchable') and grid.is_searchable(column['field']):
|
|
searchable
|
|
% endif
|
|
cell-class="c_${column['field']}"
|
|
:visible="${json.dumps(column.get('visible', True))}">
|
|
% if hasattr(grid, 'raw_renderers') and column['field'] in grid.raw_renderers:
|
|
${grid.raw_renderers[column['field']]()}
|
|
% elif grid.is_linked(column['field']):
|
|
<a :href="props.row._action_url_view"
|
|
% if view_click_handler:
|
|
@click.prevent="${view_click_handler}"
|
|
% endif
|
|
v-html="props.row.${column['field']}">
|
|
</a>
|
|
% else:
|
|
<span v-html="props.row.${column['field']}"></span>
|
|
% endif
|
|
</${b}-table-column>
|
|
% endfor
|
|
|
|
% if grid.actions:
|
|
<${b}-table-column field="actions"
|
|
label="Actions"
|
|
v-slot="props">
|
|
## TODO: we do not currently differentiate for "main vs. more"
|
|
## here, but ideally we would tuck "more" away in a drawer etc.
|
|
% for action in grid.actions:
|
|
<a v-if="props.row._action_url_${action.key}"
|
|
:href="props.row._action_url_${action.key}"
|
|
class="grid-action${' has-text-danger' if action.key == 'delete' else ''} ${action.link_class or ''}"
|
|
% if getattr(action, 'click_handler', None):
|
|
@click.prevent="${action.click_handler}"
|
|
% endif
|
|
% if getattr(action, 'target', None):
|
|
target="${action.target}"
|
|
% endif
|
|
>
|
|
${action.render_icon_and_label()}
|
|
</a>
|
|
|
|
% endfor
|
|
</${b}-table-column>
|
|
% endif
|
|
|
|
<template #empty>
|
|
<section class="section">
|
|
<div class="content has-text-grey has-text-centered">
|
|
<p>
|
|
<b-icon
|
|
pack="fas"
|
|
icon="sad-tear"
|
|
size="is-large">
|
|
</b-icon>
|
|
</p>
|
|
<p>Nothing here.</p>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<template #footer>
|
|
<div style="display: flex; justify-content: space-between;">
|
|
|
|
% if getattr(grid, 'expose_direct_link', False):
|
|
<b-button type="is-primary"
|
|
size="is-small"
|
|
@click="copyDirectLink()"
|
|
title="Copy link to clipboard">
|
|
% if request.use_oruga:
|
|
<o-icon icon="share-alt" />
|
|
% else:
|
|
<span><i class="fa fa-share-alt"></i></span>
|
|
% endif
|
|
</b-button>
|
|
% else:
|
|
<div></div>
|
|
% endif
|
|
|
|
% if grid.paginated:
|
|
<div v-if="firstItem"
|
|
style="display: flex; gap: 0.5rem; align-items: center;">
|
|
<span>
|
|
showing
|
|
{{ firstItem.toLocaleString('en') }} - {{ lastItem.toLocaleString('en') }}
|
|
of {{ total.toLocaleString('en') }} results;
|
|
</span>
|
|
<b-select v-model="perPage"
|
|
size="is-small"
|
|
@input="perPageUpdated">
|
|
% for value in grid.get_pagesize_options():
|
|
<option value="${value}">${value}</option>
|
|
% endfor
|
|
</b-select>
|
|
<span>
|
|
per page
|
|
</span>
|
|
</div>
|
|
% endif
|
|
|
|
</div>
|
|
</template>
|
|
|
|
</${b}-table>
|
|
|
|
## dummy input field needed for sharing links on *insecure* sites
|
|
% if getattr(request, 'scheme', None) == 'http':
|
|
<b-input v-model="shareLink" ref="shareLink" v-show="shareLink"></b-input>
|
|
% endif
|
|
|
|
</div>
|
|
</script>
|
|
|
|
<script type="text/javascript">
|
|
|
|
let ${grid.vue_component}CurrentData = ${json.dumps(grid.get_vue_data())|n}
|
|
|
|
let ${grid.vue_component}Data = {
|
|
loading: false,
|
|
ajaxDataUrl: ${json.dumps(getattr(grid, 'ajax_data_url', request.path_url))|n},
|
|
|
|
savingDefaults: false,
|
|
|
|
data: ${grid.vue_component}CurrentData,
|
|
rowStatusMap: ${json.dumps(grid_data['row_status_map'] if grid_data is not Undefined else {})|n},
|
|
|
|
checkable: ${json.dumps(getattr(grid, 'checkboxes', False))|n},
|
|
% if getattr(grid, 'checkboxes', False):
|
|
checkedRows: ${grid_data['checked_rows_code']|n},
|
|
% endif
|
|
|
|
% if grid.paginated:
|
|
paginated: ${json.dumps(grid.paginated)|n},
|
|
total: ${len(grid_data['data']) if static_data else (grid_data['total_items'] if grid_data is not Undefined else 0)},
|
|
perPage: ${json.dumps(grid.pagesize if grid.paginated else None)|n},
|
|
currentPage: ${json.dumps(grid.page if grid.paginated else None)|n},
|
|
firstItem: ${json.dumps(grid_data['first_item'] if grid.paginated else None)|n},
|
|
lastItem: ${json.dumps(grid_data['last_item'] if grid.paginated else None)|n},
|
|
% endif
|
|
|
|
% if getattr(grid, 'sortable', False):
|
|
|
|
## 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},
|
|
% else:
|
|
sortingPriority: [],
|
|
% endif
|
|
|
|
% endif
|
|
|
|
## filterable: ${json.dumps(grid.filterable)|n},
|
|
filters: ${json.dumps(filters_data if getattr(grid, 'filterable', False) else None)|n},
|
|
filtersSequence: ${json.dumps(filters_sequence if getattr(grid, 'filterable', False) else None)|n},
|
|
addFilterTerm: '',
|
|
addFilterShow: false,
|
|
|
|
## dummy input value needed for sharing links on *insecure* sites
|
|
% if getattr(request, 'scheme', None) == 'http':
|
|
shareLink: null,
|
|
% endif
|
|
}
|
|
|
|
let ${grid.vue_component} = {
|
|
template: '#${grid.vue_tagname}-template',
|
|
|
|
mixins: [FormPosterMixin],
|
|
|
|
props: {
|
|
csrftoken: String,
|
|
},
|
|
|
|
computed: {
|
|
|
|
addFilterChoices() {
|
|
// nb. this returns all choices available for "Add Filter" operation
|
|
|
|
// collect all filters, which are *not* already shown
|
|
let choices = []
|
|
for (let field of this.filtersSequence) {
|
|
let filtr = this.filters[field]
|
|
if (!filtr.visible) {
|
|
choices.push(filtr)
|
|
}
|
|
}
|
|
|
|
// parse list of search terms
|
|
let terms = []
|
|
for (let term of this.addFilterTerm.toLowerCase().split(' ')) {
|
|
term = term.trim()
|
|
if (term) {
|
|
terms.push(term)
|
|
}
|
|
}
|
|
|
|
// only filters matching all search terms are presented
|
|
// as choices to the user
|
|
return choices.filter(option => {
|
|
let label = option.label.toLowerCase()
|
|
for (let term of terms) {
|
|
if (label.indexOf(term) < 0) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
},
|
|
|
|
// note, can use this with v-model for hidden 'uuids' fields
|
|
selected_uuids: function() {
|
|
return this.checkedRowUUIDs().join(',')
|
|
},
|
|
|
|
// nb. this can be overridden if needed, e.g. to dynamically
|
|
// show/hide certain records in a static data set
|
|
visibleData() {
|
|
return this.data
|
|
},
|
|
|
|
directLink() {
|
|
let params = new URLSearchParams(this.getAllParams())
|
|
return `${request.path_url}?${'$'}{params}`
|
|
},
|
|
},
|
|
|
|
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: {
|
|
|
|
formatAddFilterItem(filtr) {
|
|
if (!filtr.key) {
|
|
filtr = this.filters[filtr]
|
|
}
|
|
return filtr.label || filtr.key
|
|
},
|
|
|
|
% if getattr(grid, 'click_handlers', None):
|
|
cellClick(row, column, rowIndex, columnIndex) {
|
|
% for key in grid.click_handlers:
|
|
if (column._props.field == '${key}') {
|
|
${grid.click_handlers[key]}(row)
|
|
}
|
|
% endfor
|
|
},
|
|
% endif
|
|
|
|
copyDirectLink() {
|
|
|
|
if (navigator.clipboard) {
|
|
// this is the way forward, but requires HTTPS
|
|
navigator.clipboard.writeText(this.directLink)
|
|
|
|
} else {
|
|
// use deprecated 'copy' command, but this just
|
|
// tells the browser to copy currently-selected
|
|
// text..which means we first must "add" some text
|
|
// to screen, and auto-select that, before copying
|
|
// to clipboard
|
|
this.shareLink = this.directLink
|
|
this.$nextTick(() => {
|
|
let input = this.$refs.shareLink.$el.firstChild
|
|
input.select()
|
|
document.execCommand('copy')
|
|
// re-hide the dummy input
|
|
this.shareLink = null
|
|
})
|
|
}
|
|
|
|
this.$buefy.toast.open({
|
|
message: "Link was copied to clipboard",
|
|
type: 'is-info',
|
|
duration: 2000, // 2 seconds
|
|
})
|
|
},
|
|
|
|
addRowClass(index, className) {
|
|
|
|
// TODO: this may add duplicated name to class string
|
|
// (not a serious problem i think, but could be improved)
|
|
this.rowStatusMap[index] = (this.rowStatusMap[index] || '')
|
|
+ ' ' + className
|
|
|
|
// nb. for some reason b-table does not always "notice"
|
|
// when we update status; so we force it to refresh
|
|
this.$forceUpdate()
|
|
},
|
|
|
|
getRowClass(row, index) {
|
|
return this.rowStatusMap[index]
|
|
},
|
|
|
|
getBasicParams() {
|
|
let params = {}
|
|
% if getattr(grid, 'sortable', False):
|
|
for (let i = 1; i <= this.backendSorters.length; i++) {
|
|
params['sort'+i+'key'] = this.backendSorters[i-1].field
|
|
params['sort'+i+'dir'] = this.backendSorters[i-1].order
|
|
}
|
|
% endif
|
|
% if grid.paginated:
|
|
params.pagesize = this.perPage
|
|
params.page = this.currentPage
|
|
% endif
|
|
return params
|
|
},
|
|
|
|
getFilterParams() {
|
|
let params = {}
|
|
for (var key in this.filters) {
|
|
var filter = this.filters[key]
|
|
if (filter.active) {
|
|
params[key] = filter.value
|
|
params[key+'.verb'] = filter.verb
|
|
}
|
|
}
|
|
if (Object.keys(params).length) {
|
|
params.filter = true
|
|
}
|
|
return params
|
|
},
|
|
|
|
getAllParams() {
|
|
return {...this.getBasicParams(),
|
|
...this.getFilterParams()}
|
|
},
|
|
|
|
## TODO: i noticed buefy docs show using `async` keyword here,
|
|
## so now i am too. knowing nothing at all of if/how this is
|
|
## supposed to improve anything. we shall see i guess
|
|
async loadAsyncData(params, success, failure) {
|
|
|
|
if (params === undefined || params === null) {
|
|
params = new URLSearchParams(this.getBasicParams())
|
|
} else {
|
|
params = new URLSearchParams(params)
|
|
}
|
|
if (!params.has('partial')) {
|
|
params.append('partial', true)
|
|
}
|
|
params = params.toString()
|
|
|
|
this.loading = true
|
|
this.$http.get(`${'$'}{this.ajaxDataUrl}?${'$'}{params}`).then(({ data }) => {
|
|
if (!data.error) {
|
|
${grid.vue_component}CurrentData = data.data
|
|
this.data = ${grid.vue_component}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.savingDefaults = false
|
|
this.checkedRows = this.locateCheckedRows(data.checked_rows)
|
|
if (success) {
|
|
success()
|
|
}
|
|
} else {
|
|
this.$buefy.toast.open({
|
|
message: data.error,
|
|
type: 'is-danger',
|
|
duration: 2000, // 4 seconds
|
|
})
|
|
this.loading = false
|
|
this.savingDefaults = false
|
|
if (failure) {
|
|
failure()
|
|
}
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
this.data = []
|
|
this.total = 0
|
|
this.loading = false
|
|
this.savingDefaults = false
|
|
if (failure) {
|
|
failure()
|
|
}
|
|
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.currentPage = page
|
|
this.loadAsyncData()
|
|
},
|
|
|
|
perPageUpdated(value) {
|
|
|
|
// nb. buefy passes value, oruga passes event
|
|
if (value.target) {
|
|
value = event.target.value
|
|
}
|
|
|
|
this.loadAsyncData({
|
|
pagesize: value,
|
|
})
|
|
},
|
|
|
|
onSort(field, order, event) {
|
|
|
|
// nb. buefy passes field name, oruga passes object
|
|
if (field.field) {
|
|
field = field.field
|
|
}
|
|
|
|
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
|
|
// TODO: i mean..right? would we ever not want that?
|
|
this.currentPage = 1
|
|
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() {
|
|
this.loading = true
|
|
|
|
// use current url proper, plus reset param
|
|
let url = '?reset-to-default-filters=true'
|
|
|
|
// add current hash, to preserve that in redirect
|
|
if (location.hash) {
|
|
url += '&hash=' + location.hash.slice(1)
|
|
}
|
|
|
|
location.href = url
|
|
},
|
|
|
|
addFilterInit() {
|
|
this.addFilterShow = true
|
|
|
|
this.$nextTick(() => {
|
|
const input = this.$refs.addFilterAutocomplete.$el.querySelector('input')
|
|
input.addEventListener('keydown', this.addFilterKeydown)
|
|
this.$refs.addFilterAutocomplete.focus()
|
|
})
|
|
},
|
|
|
|
addFilterHide() {
|
|
const input = this.$refs.addFilterAutocomplete.$el.querySelector('input')
|
|
input.removeEventListener('keydown', this.addFilterKeydown)
|
|
this.addFilterTerm = ''
|
|
this.addFilterShow = false
|
|
},
|
|
|
|
addFilterKeydown(event) {
|
|
|
|
// ESC will clear searchbox
|
|
if (event.which == 27) {
|
|
this.addFilterHide()
|
|
}
|
|
},
|
|
|
|
addFilterSelect(filtr) {
|
|
this.addFilter(filtr.key)
|
|
this.addFilterHide()
|
|
},
|
|
|
|
addFilter(filter_key) {
|
|
|
|
// 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 = {}
|
|
}
|
|
|
|
// merge in actual filter params
|
|
// cf. https://stackoverflow.com/a/171256
|
|
params = {...params, ...this.getFilterParams()}
|
|
|
|
// hide inactive filters
|
|
for (var key in this.filters) {
|
|
var filter = this.filters[key]
|
|
if (!filter.active) {
|
|
filter.visible = false
|
|
}
|
|
}
|
|
|
|
// set some explicit params
|
|
params.partial = true
|
|
params.filter = true
|
|
|
|
params = new URLSearchParams(params)
|
|
this.loadAsyncData(params)
|
|
this.appliedFiltersHook()
|
|
},
|
|
|
|
appliedFiltersHook() {},
|
|
|
|
clearFilters() {
|
|
|
|
// explicitly deactivate all filters
|
|
for (var key in this.filters) {
|
|
this.filters[key].active = false
|
|
}
|
|
|
|
// then just "apply" as normal
|
|
this.applyFilters()
|
|
},
|
|
|
|
// explicitly set filters for the grid, to the given set.
|
|
// this totally overrides whatever might be current. the
|
|
// new filter set should look like:
|
|
//
|
|
// [
|
|
// {key: 'status_code',
|
|
// verb: 'equal',
|
|
// value: 1},
|
|
// {key: 'description',
|
|
// verb: 'contains',
|
|
// value: 'whatever'},
|
|
// ]
|
|
//
|
|
setFilters(newFilters) {
|
|
for (let key in this.filters) {
|
|
let filter = this.filters[key]
|
|
let active = false
|
|
for (let newFilter of newFilters) {
|
|
if (newFilter.key == key) {
|
|
active = true
|
|
filter.active = true
|
|
filter.visible = true
|
|
filter.verb = newFilter.verb
|
|
filter.value = newFilter.value
|
|
break
|
|
}
|
|
}
|
|
if (!active) {
|
|
filter.active = false
|
|
filter.visible = false
|
|
}
|
|
}
|
|
this.applyFilters()
|
|
},
|
|
|
|
saveDefaults() {
|
|
this.savingDefaults = true
|
|
|
|
// apply current filters as normal, but add special directive
|
|
this.applyFilters({'save-current-filters-as-defaults': true})
|
|
},
|
|
|
|
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
|
|
},
|
|
|
|
// when a user clicks a row, handle as if they clicked checkbox.
|
|
// note that this method is only used if table is "checkable"
|
|
rowClick(row) {
|
|
let i = this.checkedRows.indexOf(row)
|
|
if (i >= 0) {
|
|
this.checkedRows.splice(i, 1)
|
|
} else {
|
|
this.checkedRows.push(row)
|
|
}
|
|
% if getattr(grid, 'check_handler', None):
|
|
this.${grid.check_handler}(this.checkedRows, row)
|
|
% endif
|
|
},
|
|
}
|
|
}
|
|
|
|
</script>
|
|
|