154 lines
5.0 KiB
Vue
154 lines
5.0 KiB
Vue
<template>
|
|
<table class="table is-stripped is-fullwidth">
|
|
<thead>
|
|
<a-row :context="context" :columns="columnNames"
|
|
:orderable="columnsOrderable" cellTag="th"
|
|
@move="moveColumn">
|
|
<template v-if="$slots['header-head']" v-slot:head="data">
|
|
<slot name="header-head" v-bind="data"/>
|
|
</template>
|
|
<template v-if="$slots['header-tail']" v-slot:tail="data">
|
|
<slot name="header-tail" v-bind="data"/>
|
|
</template>
|
|
<template v-for="column of columns" v-bind:key="column.name"
|
|
v-slot:[column.name]="data">
|
|
<slot :name="'header-' + column.name" v-bind="data">
|
|
{{ column.label }}
|
|
<span v-if="column.help" class="icon small"
|
|
:title="column.help">
|
|
<i class="fa fa-circle-question"/>
|
|
</span>
|
|
</slot>
|
|
</template>
|
|
</a-row>
|
|
</thead>
|
|
<tbody>
|
|
<slot name="head"/>
|
|
<template v-for="(item,row) in items" :key="row">
|
|
<!-- data-index comes from AList component drag & drop -->
|
|
<a-row :context="context" :item="item" :cell="{row}" :columns="columnNames" :data-index="row"
|
|
:data-row="row"
|
|
:draggable="orderable"
|
|
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop"
|
|
@cell="onCellEvent(row, $event)">
|
|
<template v-for="[name,slot] of rowSlots" :key="slot" v-slot:[slot]="data">
|
|
<slot :name="name" v-bind="data"/>
|
|
</template>
|
|
</a-row>
|
|
</template>
|
|
<slot name="tail"/>
|
|
</tbody>
|
|
</table>
|
|
</template>
|
|
<script>
|
|
import AList from './AList.vue'
|
|
import ARow from './ARow.vue'
|
|
|
|
const Component = {
|
|
extends: AList,
|
|
components: { ARow },
|
|
//! Event:
|
|
//! - cell(event): an event occured inside cell
|
|
//! - colmove({from,to}), colmove(): columns moved
|
|
emits: ['cell', 'colmove'],
|
|
|
|
props: {
|
|
...AList.props,
|
|
//! Context object
|
|
context: {type: Object, default: () => ({})},
|
|
|
|
//! Ordered list of columns, as objects with:
|
|
//! - name: item attribute value
|
|
//! - label: display label
|
|
//! - help: help text
|
|
//! - hidden: if true, field is hidden
|
|
columns: Array,
|
|
//! If True, columns are orderable
|
|
columnsOrderable: Boolean,
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
...super.data,
|
|
// TODO: add observer
|
|
columns_: [...this.columns],
|
|
extraItem: new this.set.model(),
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
columnNames() { return this.columns_.map(c => c.name) },
|
|
columnLabels() { return this.columns_.reduce(
|
|
(labels, c) => ({...labels, [c.name]: c.label}),
|
|
{}
|
|
)},
|
|
rowSlots() {
|
|
return Object.keys(this.$slots).filter(x => x.startsWith('row-'))
|
|
.map(x => [x, x.slice(4)])
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
// TODO: use in tracklist
|
|
sortColumns(names) {
|
|
const ordered = names.map(n => this.columns_.find(c => c.name == n)).filter(c => !!c);
|
|
const remaining = this.columns_.filter(c => names.indexOf(c.name) == -1)
|
|
this.columns_ = [...ordered, ...remaining]
|
|
this.$emit('colmove')
|
|
},
|
|
|
|
/**
|
|
* Move column using provided event object (as `{from, to}`)
|
|
*/
|
|
moveColumn(event) {
|
|
const {from, to} = event
|
|
const value = this.columns_[from]
|
|
this.columns_.splice(from, 1)
|
|
this.columns_.splice(to, 0, value)
|
|
this.$emit('colmove', event)
|
|
},
|
|
|
|
/**
|
|
* React on 'cell' event, re-emitting it with additional values:
|
|
* - `set`: data set
|
|
* - `row`: row index
|
|
*
|
|
* @param {Number} row: row index
|
|
* @param {} data: cell's event data
|
|
*/
|
|
onCellEvent(row, event) {
|
|
if(event.name == 'focus')
|
|
this.focus(event.data, event.cell)
|
|
this.$emit('cell', {
|
|
...event, row,
|
|
set: this.set
|
|
})
|
|
},
|
|
|
|
/**
|
|
* Return row component at provided index
|
|
*/
|
|
getRow(row) {
|
|
const els = this.$el.querySelectorAll('tr')
|
|
for(var el of els)
|
|
if(el.__row && row == Number(el.dataset.row))
|
|
return el.__row
|
|
},
|
|
|
|
/**
|
|
* Focus on a cell
|
|
*/
|
|
focus(row, col, from=null) {
|
|
if(from)
|
|
row += from.row
|
|
row = this.getRow(row)
|
|
row && row.focus(col, from)
|
|
},
|
|
},
|
|
}
|
|
Component.props.itemTag.default = 'tr'
|
|
Component.props.listTag.default = 'tbody'
|
|
|
|
export default Component
|
|
</script>
|