mirror of
https://github.com/crater-invoice/crater.git
synced 2025-10-28 12:11:08 -04:00
init crater
This commit is contained in:
64
resources/assets/js/components/base/base-table/classes/Column.js
Executable file
64
resources/assets/js/components/base/base-table/classes/Column.js
Executable file
@ -0,0 +1,64 @@
|
||||
import { pick } from '../helpers'
|
||||
|
||||
export default class Column {
|
||||
constructor (columnComponent) {
|
||||
const properties = pick(columnComponent, [
|
||||
'show', 'label', 'dataType', 'sortable', 'sortBy', 'filterable',
|
||||
'filterOn', 'hidden', 'formatter', 'cellClass', 'headerClass', 'sortAs'
|
||||
])
|
||||
|
||||
for (const property in properties) {
|
||||
this[property] = columnComponent[property]
|
||||
}
|
||||
|
||||
this.template = columnComponent.$scopedSlots.default
|
||||
}
|
||||
|
||||
isFilterable () {
|
||||
return this.filterable
|
||||
}
|
||||
|
||||
getFilterFieldName () {
|
||||
return this.filterOn || this.show
|
||||
}
|
||||
|
||||
isSortable () {
|
||||
return this.sortable
|
||||
}
|
||||
|
||||
getSortPredicate (sortOrder, allColumns) {
|
||||
const sortFieldName = this.getSortFieldName()
|
||||
|
||||
const sortColumn = allColumns.find(column => (column.sortAs === sortFieldName || column.show === sortFieldName))
|
||||
|
||||
const dataType = sortColumn.dataType
|
||||
|
||||
if (dataType.startsWith('date') || dataType === 'numeric') {
|
||||
return (row1, row2) => {
|
||||
const value1 = row1.getSortableValue(sortFieldName)
|
||||
const value2 = row2.getSortableValue(sortFieldName)
|
||||
|
||||
if (sortOrder === 'desc') {
|
||||
return value2 < value1 ? -1 : 1
|
||||
}
|
||||
|
||||
return value1 < value2 ? -1 : 1
|
||||
}
|
||||
}
|
||||
|
||||
return (row1, row2) => {
|
||||
const value1 = row1.getSortableValue(sortFieldName)
|
||||
const value2 = row2.getSortableValue(sortFieldName)
|
||||
|
||||
if (sortOrder === 'desc') {
|
||||
return value2.localeCompare(value1)
|
||||
}
|
||||
|
||||
return value1.localeCompare(value2)
|
||||
}
|
||||
}
|
||||
|
||||
getSortFieldName () {
|
||||
return this.sortBy || this.sortAs || this.show
|
||||
}
|
||||
}
|
||||
61
resources/assets/js/components/base/base-table/classes/Row.js
Executable file
61
resources/assets/js/components/base/base-table/classes/Row.js
Executable file
@ -0,0 +1,61 @@
|
||||
import moment from 'moment'
|
||||
import { get } from '../helpers'
|
||||
|
||||
export default class Row {
|
||||
constructor (data, columns) {
|
||||
this.data = data
|
||||
this.columns = columns
|
||||
}
|
||||
|
||||
getValue (columnName) {
|
||||
return get(this.data, columnName)
|
||||
}
|
||||
|
||||
getColumn (columnName) {
|
||||
return this.columns.find(column => (column.show === columnName || column.sortAs === columnName))
|
||||
}
|
||||
|
||||
getFilterableValue (columnName) {
|
||||
const value = this.getValue(columnName)
|
||||
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return value.toString().toLowerCase()
|
||||
}
|
||||
|
||||
getSortableValue (columnName) {
|
||||
const dataType = this.getColumn(columnName).dataType
|
||||
|
||||
let value = this.getValue(columnName)
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
value = value.toLowerCase()
|
||||
}
|
||||
|
||||
if (dataType.startsWith('date')) {
|
||||
const format = dataType.replace('date:', '')
|
||||
|
||||
return moment(value, format).format('YYYYMMDDHHmmss')
|
||||
}
|
||||
|
||||
if (dataType === 'numeric') {
|
||||
return value
|
||||
}
|
||||
|
||||
return value.toString()
|
||||
}
|
||||
|
||||
passesFilter (filter) {
|
||||
return this.columns
|
||||
.filter(column => column.isFilterable())
|
||||
.map(column => this.getFilterableValue(column.getFilterFieldName()))
|
||||
.filter(filterableValue => filterableValue.indexOf(filter.toLowerCase()) >= 0)
|
||||
.length
|
||||
}
|
||||
}
|
||||
120
resources/assets/js/components/base/base-table/components/Pagination.vue
Executable file
120
resources/assets/js/components/base/base-table/components/Pagination.vue
Executable file
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<nav v-if="shouldShowPagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li :class="{ disabled: pagination.currentPage === 1 }">
|
||||
<a
|
||||
:class="{ disabled: pagination.currentPage === 1 }"
|
||||
@click="pageClicked( pagination.currentPage - 1 )"
|
||||
>
|
||||
<i class="left chevron icon">«</i>
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="hasFirst" :class="{ active: isActive(1) }" class="page-item">
|
||||
<a class="page-link" @click="pageClicked(1)">1</a>
|
||||
</li>
|
||||
<li v-if="hasFirstEllipsis"><span class="pagination-ellipsis">…</span></li>
|
||||
<li v-for="page in pages" :key="page" :class="{ active: isActive(page), disabled: page === '...' }" class="page-item">
|
||||
<a class="page-link" @click="pageClicked(page)">{{ page }}</a>
|
||||
</li>
|
||||
<li v-if="hasLastEllipsis"><span class="pagination-ellipsis">…</span></li>
|
||||
<li
|
||||
v-if="hasLast"
|
||||
:class="{ active: isActive(this.pagination.totalPages) }"
|
||||
class="page-item"
|
||||
>
|
||||
<a class="page-link" @click="pageClicked(pagination.totalPages)">
|
||||
{{ pagination.totalPages }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
:class="{ disabled: pagination.currentPage === pagination.totalPages }"
|
||||
@click="pageClicked( pagination.currentPage + 1 )"
|
||||
>
|
||||
<i class="right chevron icon">»</i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
pages () {
|
||||
return this.pagination.totalPages === undefined
|
||||
? []
|
||||
: this.pageLinks()
|
||||
},
|
||||
|
||||
hasFirst () {
|
||||
return this.pagination.currentPage >= 4 || this.pagination.totalPages < 10
|
||||
},
|
||||
|
||||
hasLast () {
|
||||
return this.pagination.currentPage <= this.pagination.totalPages - 3 || this.pagination.totalPages < 10
|
||||
},
|
||||
|
||||
hasFirstEllipsis () {
|
||||
return this.pagination.currentPage >= 4 && this.pagination.totalPages >= 10
|
||||
},
|
||||
|
||||
hasLastEllipsis () {
|
||||
return this.pagination.currentPage <= this.pagination.totalPages - 3 && this.pagination.totalPages >= 10
|
||||
},
|
||||
|
||||
shouldShowPagination () {
|
||||
if (this.pagination.totalPages === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.pagination.count === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.pagination.totalPages > 1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isActive (page) {
|
||||
const currentPage = this.pagination.currentPage || 1
|
||||
|
||||
return currentPage === page
|
||||
},
|
||||
pageClicked (page) {
|
||||
if (page === '...' ||
|
||||
page === this.pagination.currentPage ||
|
||||
page > this.pagination.totalPages ||
|
||||
page < 1) {
|
||||
return
|
||||
}
|
||||
this.$emit('pageChange', page)
|
||||
},
|
||||
|
||||
pageLinks () {
|
||||
const pages = []
|
||||
|
||||
let left = 2
|
||||
let right = this.pagination.totalPages - 1
|
||||
|
||||
if (this.pagination.totalPages >= 10) {
|
||||
left = Math.max(1, this.pagination.currentPage - 2)
|
||||
right = Math.min(this.pagination.currentPage + 2, this.pagination.totalPages)
|
||||
}
|
||||
for (let i = left; i <= right; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
24
resources/assets/js/components/base/base-table/components/TableCell.js
Executable file
24
resources/assets/js/components/base/base-table/components/TableCell.js
Executable file
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
functional: true,
|
||||
|
||||
props: ['column', 'row', 'responsiveLabel'],
|
||||
|
||||
render (createElement, { props }) {
|
||||
const data = {}
|
||||
|
||||
if (props.column.cellClass) {
|
||||
data.class = props.column.cellClass
|
||||
}
|
||||
|
||||
if (props.column.template) {
|
||||
return createElement('td', data, props.column.template(props.row.data))
|
||||
}
|
||||
|
||||
data.domProps = {}
|
||||
data.domProps.innerHTML = props.column.formatter(props.row.getValue(props.column.show), props.row.data)
|
||||
|
||||
return createElement('td', [
|
||||
createElement('span', props.responsiveLabel), data.domProps.innerHTML
|
||||
])
|
||||
}
|
||||
}
|
||||
32
resources/assets/js/components/base/base-table/components/TableColumn.vue
Executable file
32
resources/assets/js/components/base/base-table/components/TableColumn.vue
Executable file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<!-- Never render the contents -->
|
||||
<!-- The scoped slot won't have the required data -->
|
||||
<div v-if="false">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import settings from '../settings'
|
||||
export default {
|
||||
props: {
|
||||
show: { required: false, type: String },
|
||||
label: { default: null, type: String },
|
||||
dataType: { default: 'string', type: String },
|
||||
|
||||
sortable: { default: true, type: Boolean },
|
||||
sortBy: { default: null },
|
||||
|
||||
filterable: { default: true, type: Boolean },
|
||||
sortAs: { default: null },
|
||||
filterOn: { default: null },
|
||||
|
||||
formatter: { default: v => v, type: Function },
|
||||
|
||||
hidden: { default: false, type: Boolean },
|
||||
|
||||
cellClass: { default: settings.cellClass },
|
||||
headerClass: { default: settings.headerClass },
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<th
|
||||
v-if="this.isVisible"
|
||||
slot-scope="col"
|
||||
:aria-sort="ariaSort"
|
||||
:aria-disabled="ariaDisabled"
|
||||
:class="headerClass"
|
||||
role="columnheader"
|
||||
@click="clicked"
|
||||
>
|
||||
{{ label }}
|
||||
</th>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { classList } from '../helpers'
|
||||
export default {
|
||||
props: ['column', 'sort'],
|
||||
|
||||
computed: {
|
||||
ariaDisabled () {
|
||||
if (!this.column.isSortable()) {
|
||||
return 'true'
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
ariaSort () {
|
||||
if (!this.column.isSortable ()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if ((this.column.sortAs || this.column.show) !== this.sort.fieldName) {
|
||||
return 'none'
|
||||
}
|
||||
|
||||
return this.sort.order === 'asc' ? 'ascending' : 'descending';
|
||||
},
|
||||
|
||||
headerClass () {
|
||||
if (!this.column.isSortable()) {
|
||||
return classList('table-component__th', this.column.headerClass);
|
||||
}
|
||||
|
||||
if ((this.column.sortAs || this.column.show) !== this.sort.fieldName) {
|
||||
return classList('table-component__th table-component__th--sort', this.column.headerClass);
|
||||
}
|
||||
|
||||
return classList(`table-component__th table-component__th--sort-${this.sort.order}`, this.column.headerClass);
|
||||
},
|
||||
|
||||
isVisible () {
|
||||
return !this.column.hidden
|
||||
},
|
||||
|
||||
label () {
|
||||
if (this.column.label === null) {
|
||||
return this.column.show
|
||||
}
|
||||
return this.column.label
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
clicked () {
|
||||
if (this.column.isSortable()) {
|
||||
this.$emit('click', this.column)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
330
resources/assets/js/components/base/base-table/components/TableComponent.vue
Executable file
330
resources/assets/js/components/base/base-table/components/TableComponent.vue
Executable file
@ -0,0 +1,330 @@
|
||||
<template>
|
||||
<div class="table-component">
|
||||
<div v-if="showFilter && filterableColumnExists" class="table-component__filter">
|
||||
<input
|
||||
:class="fullFilterInputClass"
|
||||
v-model="filter"
|
||||
:placeholder="filterPlaceholder"
|
||||
type="text"
|
||||
>
|
||||
<a v-if="filter" class="table-component__filter__clear" @click="filter = ''">×</a>
|
||||
</div>
|
||||
|
||||
<div class="table-component__table-wrapper">
|
||||
<base-loader v-if="loading" class="table-loader" />
|
||||
|
||||
<table :class="fullTableClass">
|
||||
<caption
|
||||
v-if="showCaption"
|
||||
class="table-component__table__caption"
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
>{{ ariaCaption }}</caption>
|
||||
<thead :class="fullTableHeadClass">
|
||||
<tr>
|
||||
<table-column-header
|
||||
v-for="column in columns"
|
||||
:key="column.show || column.show"
|
||||
:sort="sort"
|
||||
:column="column"
|
||||
@click="changeSorting"
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody :class="fullTableBodyClass">
|
||||
<table-row
|
||||
v-for="row in displayedRows"
|
||||
:key="row.vueTableComponentInternalRowId"
|
||||
:row="row"
|
||||
:columns="columns"
|
||||
@rowClick="emitRowClick"
|
||||
/>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<slot :rows="rows" name="tfoot" />
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="displayedRows.length === 0 && !loading" class="table-component__message">{{ filterNoResults }}</div>
|
||||
|
||||
<div style="display:none;">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<pagination v-if="pagination && !loading" :pagination="pagination" @pageChange="pageChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Column from '../classes/Column'
|
||||
import expiringStorage from '../expiring-storage'
|
||||
import Row from '../classes/Row'
|
||||
import TableColumnHeader from './TableColumnHeader'
|
||||
import TableRow from './TableRow'
|
||||
import settings from '../settings'
|
||||
import Pagination from './Pagination'
|
||||
import { classList, pick } from '../helpers'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TableColumnHeader,
|
||||
TableRow,
|
||||
Pagination
|
||||
},
|
||||
|
||||
props: {
|
||||
data: { default: () => [], type: [Array, Function] },
|
||||
|
||||
showFilter: { type: Boolean, default: true },
|
||||
showCaption: { type: Boolean, default: true },
|
||||
|
||||
sortBy: { default: '', type: String },
|
||||
sortOrder: { default: '', type: String },
|
||||
|
||||
cacheKey: { default: null },
|
||||
cacheLifetime: { default: 5 },
|
||||
|
||||
tableClass: { default: () => settings.tableClass },
|
||||
theadClass: { default: () => settings.theadClass },
|
||||
tbodyClass: { default: () => settings.tbodyClass },
|
||||
filterInputClass: { default: () => settings.filterInputClass },
|
||||
filterPlaceholder: { default: () => settings.filterPlaceholder },
|
||||
filterNoResults: { default: () => settings.filterNoResults }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
columns: [],
|
||||
rows: [],
|
||||
filter: '',
|
||||
sort: {
|
||||
fieldName: '',
|
||||
order: ''
|
||||
},
|
||||
pagination: null,
|
||||
|
||||
loading: false,
|
||||
localSettings: {}
|
||||
}),
|
||||
|
||||
computed: {
|
||||
fullTableClass () {
|
||||
return classList('table-component__table', this.tableClass)
|
||||
},
|
||||
|
||||
fullTableHeadClass () {
|
||||
return classList('table-component__table__head', this.theadClass)
|
||||
},
|
||||
|
||||
fullTableBodyClass () {
|
||||
return classList('table-component__table__body', this.tbodyClass)
|
||||
},
|
||||
|
||||
fullFilterInputClass () {
|
||||
return classList('table-component__filter__field', this.filterInputClass)
|
||||
},
|
||||
|
||||
ariaCaption () {
|
||||
if (this.sort.fieldName === '') {
|
||||
return 'Table not sorted'
|
||||
}
|
||||
|
||||
return (
|
||||
`Table sorted by ${this.sort.fieldName} ` +
|
||||
(this.sort.order === 'asc' ? '(ascending)' : '(descending)')
|
||||
)
|
||||
},
|
||||
|
||||
usesLocalData () {
|
||||
return Array.isArray(this.data)
|
||||
},
|
||||
|
||||
displayedRows () {
|
||||
if (!this.usesLocalData) {
|
||||
return this.sortedRows
|
||||
}
|
||||
|
||||
if (!this.showFilter) {
|
||||
return this.sortedRows
|
||||
}
|
||||
|
||||
if (!this.columns.filter(column => column.isFilterable()).length) {
|
||||
return this.sortedRows
|
||||
}
|
||||
|
||||
return this.sortedRows.filter(row => row.passesFilter(this.filter))
|
||||
},
|
||||
|
||||
sortedRows () {
|
||||
if (!this.usesLocalData) {
|
||||
return this.rows
|
||||
}
|
||||
|
||||
if (this.sort.fieldName === '') {
|
||||
return this.rows
|
||||
}
|
||||
|
||||
if (this.columns.length === 0) {
|
||||
return this.rows
|
||||
}
|
||||
|
||||
const sortColumn = this.getColumn(this.sort.fieldName)
|
||||
|
||||
if (!sortColumn) {
|
||||
return this.rows
|
||||
}
|
||||
|
||||
return this.rows.sort(
|
||||
sortColumn.getSortPredicate(this.sort.order, this.columns)
|
||||
)
|
||||
},
|
||||
|
||||
filterableColumnExists () {
|
||||
return this.columns.filter(c => c.isFilterable()).length > 0
|
||||
},
|
||||
|
||||
storageKey () {
|
||||
return this.cacheKey
|
||||
? `vue-table-component.${this.cacheKey}`
|
||||
: `vue-table-component.${window.location.host}${window.location.pathname}${this.cacheKey}`
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
filter () {
|
||||
if (!this.usesLocalData) {
|
||||
this.mapDataToRows()
|
||||
}
|
||||
|
||||
this.saveState()
|
||||
},
|
||||
|
||||
data () {
|
||||
if (this.usesLocalData) {
|
||||
this.mapDataToRows()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.sort.order = this.sortOrder
|
||||
|
||||
this.restoreState()
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
this.sort.fieldName = this.sortBy
|
||||
const columnComponents = this.$slots.default
|
||||
.filter(column => column.componentInstance)
|
||||
.map(column => column.componentInstance)
|
||||
|
||||
this.columns = columnComponents.map(column => new Column(column))
|
||||
|
||||
columnComponents.forEach(columnCom => {
|
||||
Object.keys(columnCom.$options.props).forEach(prop =>
|
||||
columnCom.$watch(prop, () => {
|
||||
this.columns = columnComponents.map(column => new Column(column))
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
await this.mapDataToRows()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async pageChange (page) {
|
||||
this.pagination.currentPage = page
|
||||
|
||||
await this.mapDataToRows()
|
||||
},
|
||||
|
||||
async mapDataToRows () {
|
||||
const data = this.usesLocalData
|
||||
? this.prepareLocalData()
|
||||
: await this.fetchServerData()
|
||||
|
||||
let rowId = 0
|
||||
|
||||
this.rows = data
|
||||
.map(rowData => {
|
||||
rowData.vueTableComponentInternalRowId = rowId++
|
||||
return rowData
|
||||
})
|
||||
.map(rowData => new Row(rowData, this.columns))
|
||||
},
|
||||
|
||||
prepareLocalData () {
|
||||
this.pagination = null
|
||||
|
||||
return this.data
|
||||
},
|
||||
|
||||
async fetchServerData () {
|
||||
const page = (this.pagination && this.pagination.currentPage) || 1
|
||||
this.loading = true
|
||||
|
||||
const response = await this.data({
|
||||
filter: this.filter,
|
||||
sort: this.sort,
|
||||
page: page
|
||||
})
|
||||
|
||||
this.pagination = response.pagination
|
||||
this.loading = false
|
||||
return response.data
|
||||
},
|
||||
|
||||
async refresh () {
|
||||
if (this.pagination) {
|
||||
this.pagination.currentPage = 1
|
||||
}
|
||||
await this.mapDataToRows()
|
||||
},
|
||||
|
||||
changeSorting (column) {
|
||||
if (this.sort.fieldName !== (column.sortAs || column.show)) {
|
||||
this.sort.fieldName = (column.sortAs || column.show)
|
||||
this.sort.order = 'asc'
|
||||
} else {
|
||||
this.sort.order = this.sort.order === 'asc' ? 'desc' : 'asc'
|
||||
}
|
||||
|
||||
if (!this.usesLocalData) {
|
||||
this.mapDataToRows()
|
||||
}
|
||||
|
||||
this.saveState()
|
||||
},
|
||||
|
||||
getColumn (columnName) {
|
||||
return this.columns.find(column => column.show === columnName)
|
||||
},
|
||||
|
||||
saveState () {
|
||||
expiringStorage.set(
|
||||
this.storageKey,
|
||||
pick(this.$data, ['filter', 'sort']),
|
||||
this.cacheLifetime
|
||||
)
|
||||
},
|
||||
|
||||
restoreState () {
|
||||
const previousState = expiringStorage.get(this.storageKey)
|
||||
|
||||
if (previousState === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.sort = previousState.sort
|
||||
this.filter = previousState.filter
|
||||
|
||||
this.saveState()
|
||||
},
|
||||
|
||||
emitRowClick (row) {
|
||||
this.$emit('rowClick', row)
|
||||
this.$emit('row-click', row)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
38
resources/assets/js/components/base/base-table/components/TableRow.vue
Executable file
38
resources/assets/js/components/base/base-table/components/TableRow.vue
Executable file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<tr @click="onClick">
|
||||
<table-cell
|
||||
v-for="column in visibleColumns"
|
||||
:row="row"
|
||||
:column="column"
|
||||
:key="column.id"
|
||||
:responsive-label="column.label"
|
||||
></table-cell>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TableCell from './TableCell';
|
||||
|
||||
export default {
|
||||
props: ['columns', 'row'],
|
||||
|
||||
components: {
|
||||
TableCell,
|
||||
},
|
||||
|
||||
computed: {
|
||||
visibleColumns() {
|
||||
return this.columns.filter(column => ! column.hidden);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick(e) {
|
||||
this.$emit('rowClick', {
|
||||
e,
|
||||
row: this.row
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
34
resources/assets/js/components/base/base-table/expiring-storage.js
Executable file
34
resources/assets/js/components/base/base-table/expiring-storage.js
Executable file
@ -0,0 +1,34 @@
|
||||
class ExpiringStorage {
|
||||
get (key) {
|
||||
const cached = JSON.parse(
|
||||
localStorage.getItem(key)
|
||||
)
|
||||
|
||||
if (!cached) {
|
||||
return null
|
||||
}
|
||||
|
||||
const expires = new Date(cached.expires)
|
||||
|
||||
if (expires < new Date()) {
|
||||
localStorage.removeItem(key)
|
||||
return null
|
||||
}
|
||||
|
||||
return cached.value
|
||||
}
|
||||
|
||||
has (key) {
|
||||
return this.get(key) !== null
|
||||
}
|
||||
|
||||
set (key, value, lifeTimeInMinutes) {
|
||||
const currentTime = new Date().getTime()
|
||||
|
||||
const expires = new Date(currentTime + lifeTimeInMinutes * 60000)
|
||||
|
||||
localStorage.setItem(key, JSON.stringify({ value, expires }))
|
||||
}
|
||||
}
|
||||
|
||||
export default new ExpiringStorage()
|
||||
30
resources/assets/js/components/base/base-table/helpers.js
Executable file
30
resources/assets/js/components/base/base-table/helpers.js
Executable file
@ -0,0 +1,30 @@
|
||||
export function classList (...classes) {
|
||||
return classes
|
||||
.map(c => Array.isArray(c) ? c : [c])
|
||||
.reduce((classes, c) => classes.concat(c), [])
|
||||
}
|
||||
|
||||
export function get (object, path) {
|
||||
if (!path) {
|
||||
return object
|
||||
}
|
||||
|
||||
if (object === null || typeof object !== 'object') {
|
||||
return object
|
||||
}
|
||||
|
||||
const [pathHead, pathTail] = path.split(/\.(.+)/)
|
||||
|
||||
return get(object[pathHead], pathTail)
|
||||
}
|
||||
|
||||
export function pick (object, properties) {
|
||||
return properties.reduce((pickedObject, property) => {
|
||||
pickedObject[property] = object[property]
|
||||
return pickedObject
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function range (from, to) {
|
||||
return [...Array(to - from)].map((_, i) => i + from)
|
||||
}
|
||||
20
resources/assets/js/components/base/base-table/index.js
Executable file
20
resources/assets/js/components/base/base-table/index.js
Executable file
@ -0,0 +1,20 @@
|
||||
import TableComponent from './components/TableComponent'
|
||||
import TableColumn from './components/TableColumn'
|
||||
import Pagination from './components/Pagination'
|
||||
import { mergeSettings } from './settings'
|
||||
|
||||
export default {
|
||||
install (Vue, options = {}) {
|
||||
mergeSettings(options)
|
||||
|
||||
Vue.component('table-component', TableComponent)
|
||||
Vue.component('table-column', TableColumn)
|
||||
Vue.component('pagination', Pagination)
|
||||
},
|
||||
|
||||
settings (settings) {
|
||||
mergeSettings(settings)
|
||||
}
|
||||
}
|
||||
|
||||
export { TableComponent, TableColumn }
|
||||
18
resources/assets/js/components/base/base-table/settings.js
Executable file
18
resources/assets/js/components/base/base-table/settings.js
Executable file
@ -0,0 +1,18 @@
|
||||
const settings = {
|
||||
tableClass: '',
|
||||
theadClass: '',
|
||||
tbodyClass: '',
|
||||
headerClass: '',
|
||||
cellClass: '',
|
||||
filterInputClass: '',
|
||||
filterPlaceholder: 'Filter table…',
|
||||
filterNoResults: 'There are no matching rows'
|
||||
}
|
||||
|
||||
export function mergeSettings (newSettings) {
|
||||
for (const setting in newSettings) {
|
||||
settings[setting] = newSettings[setting]
|
||||
}
|
||||
}
|
||||
|
||||
export default settings
|
||||
Reference in New Issue
Block a user