Vue.js/Bulma datatable
Vue.js/Bulma sortable/filterable table.
by jeffory
HTML
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/lodash.js"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/bulma.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/font-awesome.css">
<div id="app">
<p class="control">
<input v-model="search" placeholder="search" class="input">
</p>
<data-table inline-template :data="table_data" :columns="table_columns" :query="search">
<div class="datatable">
<table class="table">
<thead>
<tr>
<th v-for="column, index in view_columns">
<span>{{ column }}</span>
<div class="column-controls">
<span @click="column_toggle_sort(index)" class="icon is-small">
<i v-if="sort.index == index && sort.desc" class="fa fa-sort-desc"></i>
<i v-else-if="sort.index == index && !sort.desc" class="fa fa-sort-asc"></i>
<i v-else class="fa fa-sort"></i>
</span>
</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in view_data">
<td v-for="column in row">
{{ column }}
</td>
</tr>
<tr v-if="view_data.length == 0">
<td :colspan="view_columns.length" class="has-text-centered">No data specified.</td>
</tr>
</tbody>
</table>
</div>
</data-table>
</div>
CSS
#app {
margin: 10px;
}
.datatable {
width: 100%
}
.column-controls {
cursor: pointer;
float: right;
margin-top: 4px;
}
.column-controls .fa-sort {
opacity: 0.4;
}
JavaScript
Vue.component('data-table', {
props: [
'data', 'columns', 'query', 'controls_column'
],
data () {
return {
view_columns: [],
view_data: [],
data_map: {},
slot_map: {},
sort: {
index: 0,
desc: true
}
}
},
created() {
this.parse_columns();
this.parse_data();
this.sort_data();
},
methods: {
parse_columns() {
this.columns.forEach((column, index) => {
if (typeof(column) == 'string') {
return this.view_columns.push(column);
}
this.view_columns.push(column[0]);
if (column[1].startsWith('!')) {
// This is a slot.
} else if (column.length > 1) {
// So this would make a map like { column_index: field }
this.data_map[index] = column[1];
}
});
},
column_toggle_sort(column_index) {
if (column_index == this.sort.index) {
this.sort.desc = !this.sort.desc;
} else {
this.sort.index = column_index;
this.sort.desc = true;
}
this.sort_data();
},
parse_data() {
if (this.data == undefined) {
return;
}
this.view_data = [];
this.data.forEach((row_object) => {
let row = [];
// Assume an object if the data_map is set. Otherwise parse as a straight array.
if (Object.keys(this.data_map).length > 0) {
Object.keys(this.data_map).forEach((key) => {
let value = this.data_map[key];
if (value in row_object) {
row.push(row_object[value]);
}
});
} else {
row = row_object;
}
// Filter if a query is provided.
if (typeof(this.query) !== "undefined" && this.query !== "") {
let matched = false;
row.forEach((cell) => {
let query = String(this.query).toLowerCase().trim();
if (String(cell).toLowerCase().indexOf(query) !== -1) {
...