For sure - its still very early in development, so its not super usable (yet) but here’s the full details:
import { BaseModel } from 'types/BaseModel';
import { MdcSnackbarService } from '@aurelia-mdc-web/snackbar';
import { BaseService } from 'services/api/BaseService';
import { DialogService } from 'aurelia-dialog';
import { bindable, inject } from 'aurelia-framework';
import { ValidationController } from 'aurelia-validation';
import { Field } from 'types/Field';
import debug from 'debug';
const logger = debug('app/components/admin/admin-table');
function titleize(key: string) {
const split = key
.replace(/_/g, ' ')
.replace(/([A-Z])/g, " $1");
return split.charAt(0).toUpperCase() + split.slice(1);
}
@inject(
DialogService,
MdcSnackbarService,
)
export class AdminTable {
@bindable _fields: Field[]
@bindable RowType: any;
@bindable service: BaseService;
@bindable DialogViewModel: any;
@bindable controller: ValidationController;
@bindable rows?: BaseModel[];
@bindable params = {};
@bindable perPage = 10;
@bindable page = 1;
@bindable total = 0;
@bindable title: string;
constructor(
protected dialogService: DialogService,
protected snackbarService: MdcSnackbarService,
) {
}
attached() {
this.fetchRows();
}
perPageChanged(val) {
this.params = {
...this.params,
$limit: val,
}
}
pageChanged(val) {
this.params = {
...this.params,
$skip: (val-1) * this.perPage,
}
}
paramsChanged() {
this.fetchRows();
}
fetchRows() {
if (!this.service) {
logger('Missing required property: service')
return;
}
return this.service.service.find({ query: this.params })
.then(result => {
Object.assign(this, {
total: result.total,
rows: result.data.map(row => new this.RowType(row)),
})
if (!this.fields && result.data.length) {
this.fields = this.getFieldsFromRow(result.data[0])
}
});
}
set fields(fields: Field[]) {
this._fields = fields;
}
get fields() : Field[] {
if (!this._fields && this.rows?.length) {
return this.getFieldsFromRow(this.rows[0])
}
return this._fields || [];
}
getFieldsFromRow(row: BaseModel) {
return Object.keys(row).map(key => ({
label: titleize(key),
name: key,
}))
}
create() {
return this.edit({} as BaseModel)
}
edit(item: BaseModel) {
return this.dialogService.open({
viewModel: this.DialogViewModel,
model: {...item},
}).whenClosed(result => {
if (result.wasCancelled) {
return;
}
if (item.id) {
for (let i = 0; i < this.rows.length; i++){
if (this.rows[i].id === item.id) {
this.rows = this.rows.map(row => {
if (row.id === item.id) {
return result.output;
}
return row;
})
break;
}
}
} else {
this.rows = [
...this.rows,
new this.RowType(result.output),
]
}
})
}
getSelectedRows() : BaseModel[] {
return this.rows.filter(row => !!row.__checked);
}
deleteSelected() {
const selected = this.getSelectedRows()
if (!selected.length) {
this.snackbarService.open(`No ${this.title}(s) selected`)
return;
}
return this.delete(selected)
}
delete(users: BaseModel[]) {
return Promise.all(users.map(user => {
return this.service.service.remove(user.id)
.then(() => this.params = {...this.params})
})).then(() => this.snackbarService.open(`${this.title}(s) deleted`))
.catch(e => {
logger(e)
this.snackbarService.open(e);
})
}
}
<template>
<template replaceable part="table">
<mdc-data-table class="admin">
<mdc-data-table-header>
<mdc-data-table-header-cell>
<mdc-checkbox></mdc-checkbox>
</mdc-data-table-header-cell>
<mdc-data-table-header-cell>Edit</mdc-data-table-header-cell>
<mdc-data-table-header-cell repeat.for="field of fields">${field.label}</mdc-data-table-header-cell>
</mdc-data-table-header>
<mdc-data-table-content>
<mdc-data-table-row repeat.for="row of rows">
<mdc-data-table-cell>
<mdc-checkbox checked.bind="row.__checked"></mdc-checkbox>
</mdc-data-table-cell>
<mdc-data-table-cell>
<button click.delegate="edit(row)" mdc-icon-button icon="edit"></button>
</mdc-data-table-cell>
<mdc-data-table-cell repeat.for="field of fields">${row[field.name]}</mdc-data-table-cell>
</mdc-data-table-row>
</mdc-data-table-content>
</mdc-data-table>
</template>
<div class="admin-actions">
<button mdc-button type="button" click.delegate="create()">
<mdc-icon>add</mdc-icon> Create ${title}
</button>
<button mdc-button type="button" click.delegate="deleteSelected()">
<mdc-icon>delete</mdc-icon> Delete Selected
</button>
</div>
<mdc-pagination per-page.two-way="perPage" page.two-way="page" total.bind="total">
</mdc-pagination>
</template>
Example usage:
<mdc-expandable accordion="admin">
<div slot="caption">Manage Users</div>
<admin-table row-type.bind="User" service.bind="usersService" dialog-view-model.bind="UserEditViewModel"
title="User">
<template replace-part="table">
<mdc-data-table class="admin">
<mdc-data-table-header>
<mdc-data-table-header-cell>
<mdc-checkbox></mdc-checkbox>
</mdc-data-table-header-cell>
<mdc-data-table-header-cell>Edit</mdc-data-table-header-cell>
<mdc-data-table-header-cell>Name</mdc-data-table-header-cell>
<mdc-data-table-header-cell>Username</mdc-data-table-header-cell>
<mdc-data-table-header-cell>Email</mdc-data-table-header-cell>
<mdc-data-table-header-cell>Roles</mdc-data-table-header-cell>
</mdc-data-table-header>
<mdc-data-table-content>
<mdc-data-table-row repeat.for="row of rows">
<mdc-data-table-cell>
<mdc-checkbox checked.bind="row.__checked"></mdc-checkbox>
</mdc-data-table-cell>
<mdc-data-table-cell>
<button click.delegate="edit(row)" mdc-icon-button icon="edit"></button>
</mdc-data-table-cell>
<mdc-data-table-cell>${row.fullname}</mdc-data-table-cell>
<mdc-data-table-cell>${row.username}</mdc-data-table-cell>
<mdc-data-table-cell>${row.email}</mdc-data-table-cell>
<mdc-data-table-cell>
<mdc-form-field repeat.for="role of roles">
<mdc-checkbox checked.to-view="row.hasRole(role)"
change.delegate="updateUserRole(row, role, $event.target.checked)">
</mdc-checkbox>
<label>${role.role_name}</label>
</mdc-form-field>
</mdc-data-table-cell>
</mdc-data-table-row>
</mdc-data-table-content>
</mdc-data-table>
</template>
</admin-table>
</mdc-expandable>