I am new to Vue and have never tried creating my own datatable, which brings me where I am now, suck.
I am trying to create a table where we can have dynamic links and/or buttons on any cell in any row. Mostly it will just end up being a few links in the last cell: edit / delete. I have the table to the point where I can pass in the columns and rows and it will build the table out visually. The filter and paging also work fine. Where I am suck is, how do I update and refresh the table to display new data when something is added or deleted? Plus how do I get the edit and delete buttons to work? Any help or criticism would be appreciated .
Here is my vue page I set up to just test the datatable:
<template>
<div>
<div style="margin-bottom:25px;">
<button type="button" @click="addNew()">Add New</button>
</div>
<h-table :headers="tableHeaders" :rows="tableRows"></h-table>
</div>
</template>
<script>
import { ref, onBeforeMount, watch } from "vue";
import { dataTable } from '../shared';
import HTable from '@/components/form-components/h-table';
export default {
components: {
HTable,
},
setup() {
let id = ref(3);
const tableData = ref([
{
id: 1,
name: "Mark",
color: "Orange"
},
{
id: 2,
name: "David",
color: "Red"
},
{
id: 3,
name: "Ashley",
color: "Pink"
},
]);
const tableHeaders = [
{ label: "One" },
{ label: "Two" },
{ label: "Three" },
{ label: "" } //Links
];
let tableRows = [];
const buildTableRows = () => {
//Clear the data
tableRows.value = [];
for (var i = 0; i < tableData.value.length; i++) {
var row = dataTable.buildRow();
//Column One
dataTable.buildColumn(row, tableData.value[i].id);
//Column Two
dataTable.buildColumn(row, tableData.value[i].name);
//Column Three
dataTable.buildColumn(row, tableData.value[i].color);
//Column Four
var colThree = dataTable.buildColumn(row, "");
dataTable.buildLink(colThree, "Edit", "", `editItem(${tableData.value[i].id})`);
dataTable.buildLink(colThree, "Delete", "");
tableRows.push(row);
}
}
const addNew = () => {
const newTest = {
id: id + 1,
name: `Name-${id + 1}`,
color: `Color-${id + 1}`
};
tableData.value.push(newTest);
}
const editItem = (id) => {
console.log(id);
};
const deleteItem = (id) => {
console.log(id);
};
watch(tableData, () => { buildTableRows(); }, { deep: true });
onBeforeMount(() => {
buildTableRows();
});
return { tableHeaders, tableRows, addNew, editItem, deleteItem }
}
}
</script>
And my data-table.js file
const paginate = (fromArr, pageActive, limitPerPage) => {
var newArr = Array.from(fromArr);
var startPaginate = Number(limitPerPage) * Number(pageActive) - (Number(limitPerPage) - 1);
var endPaginate = Number(limitPerPage) * Number(pageActive);
return newArr.slice(startPaginate - 1, endPaginate <= newArr.length ? endPaginate : newArr.length);
};
const pageInfo = (fromArr, pageActive, limitPerPage) => {
var newArr = Array.from(fromArr);
var startPaginate = Number(limitPerPage) * Number(pageActive) - (Number(limitPerPage) - 1);
var endPaginate = Number(limitPerPage) * Number(pageActive);
return {
from: newArr.length >= 1 ? startPaginate : 0,
start: newArr.length >= 1 ? startPaginate : 0,
to: endPaginate <= newArr.length ? endPaginate : newArr.length,
end: endPaginate <= newArr.length ? endPaginate : newArr.length,
of: newArr.length,
length: newArr.length
};
}
const pages = (fromArr, limitPerPage) => {
var newArr = Array.from(fromArr);
var divideLength = newArr.length / Number(limitPerPage);
var pageNumber = Math.ceil(divideLength);
return pageNumber;
}
const pagination = (totalPages) => {
var newArr = [];
for (var i = 1; i < totalPages + 1; i++) {
newArr.push(i);
}
return newArr;
};
const search = (fromArr, searchTerm) => {
var testing = fromArr.flatMap(obj => {
const objHasSearchTerm = Object.entries(obj)
.some(([key, value]) => key !== 'columns' && String(value).toLowerCase().includes(searchTerm.toLowerCase()));
if (objHasSearchTerm && !obj.cells) {
return [obj];
}
const matchedChildren = search(obj.columns ?? [], searchTerm);
return objHasSearchTerm || matchedChildren.length > 0
? obj
: [];
});
return testing;
}
const buildRow = (id) => {
var row = {};
row.id = id;
row.columns = [];
return row;
}
const buildColumn = (row, columnData) => {
var column = {};
column.columnData = columnData;
column.links = [];
column.buttons = [];
row.columns.push(column);
return column;
}
const buildLink = (column, text, url, clickFunction) => {
var link = {};
link.text = text;
link.url = url;
link.click = clickFunction;
column.links.push(link);
}
const buildButton = (column, text, url, clickFunction) => {
var button = {};
button.text = text;
button.url = url;
button.click = clickFunction;
column.buttons.push(button);
}
export const dataTable = {
paginate,
pageInfo,
pages,
pagination,
search,
buildRow,
buildColumn,
buildLink,
buildButton
}
And lastly my datatable component:
<template>
<div class="datatable">
<div class="dataTables_length">
<label>
Show
<select v-model="entriesToDisplay" @change="paginiation">
<option value="5">5</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
entries
</label>
</div>
<div class="dataTables_filter">
<label>
Filter:
<input type="search" v-model="searchInput" class="" placeholder="" @keyup="search">
</label>
</div>
<table class="data-table">
<thead>
<tr>
<th v-for="header in headers" :key="header.label">{{header.label}}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in filteredRows" :key="row.id" ref="rows">
<td v-for="(column, index) in row.columns" :key="index">
<div v-html="column.columnData"></div>
<ul v-if="column.links">
<li v-for="(link, index) in column.links" :key="index">
<a @click="link.click">{{link.text}}</a>
</li>
</ul>
<ul>
<li v-for="(button, index) in column.buttons" :key="index">
<button @click="button.click">{{button.text}}</button>
</li>
</ul>
</td>
</tr>
<tr v-if="!entries.length" class="no-table-data">
<td :colspan="headers.length">No Data</td>
</tr>
</tbody>
</table>
<div class="dataTables_info" id="DataTables_Table_0_info" role="status" aria-live="polite">Showing {{ viewInfo.from }} to {{ viewInfo.to }} of {{ viewInfo.of }} entries <span v-if="searchInput">(filtered from {{ rows.length }} total entries)</span></div>
<div class="dataTables_paginate paging_simple_numbers" id="DataTables_Table_0_paginate">
<a class="paginate_button previous" :class="{'disabled': currentPage === 1}" @click="paginateEntries('previous', page)">Previous</a>
<span v-for="page in pages" :key="page">
<a class="paginate_button" :class="{'current': currentPage === page}" @click="paginateEntries('page', page)">{{page}}</a>
</span>
<a class="paginate_button next" :class="{'disabled': currentPage === totalPages}" @click="paginateEntries('next', page)">Next</a>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue';
import { dataTable } from '../../shared';
export default {
props: {
id: String,
headers: ref({}),
rows: ref({})
},
setup(props) {
let entries = ref([...props.rows]);
let currentPage = ref(1);
let searchInput = ref("");
let entriesToDisplay = ref(5);
let filteredRows = ref(dataTable.paginate(entries.value, 1, entriesToDisplay.value));
let paginiation = () => filteredRows.value = dataTable.paginate(entries.value, 1, entriesToDisplay.value);
let totalPages = computed(() => dataTable.pages(entries.value, entriesToDisplay.value))
const viewInfo = computed(() => dataTable.pageInfo(entries.value, 1, entriesToDisplay.value));
let pages = computed(() => dataTable.pagination(totalPages.value));
const search = () => {
currentPage = 1;
entries.value = dataTable.search(props.rows, searchInput.value);
paginateData(entries.value);
paginiation();
}
const paginateData = (data) => {
filteredRows.value = dataTable.paginate(data, currentPage.value, entriesToDisplay.value);
totalPages.value = dataTable.pages(data, entriesToDisplay.value);
}
const paginateEntries = (action, page) => {
switch (action) {
case "previous":
(currentPage.value == 1) ? currentPage.value = 1 : currentPage.value -= 1;
break;
case "next":
(currentPage.value == totalPages.value) ? currentPage.value = totalPages.value : currentPage.value += 1;
break;
case "page":
currentPage.value = page
break;
}
entries.value = [...props.rows];
paginateData(entries.value);
};
return {
entries,
entriesToDisplay,
currentPage,
searchInput,
filteredRows,
paginiation,
search,
paginateData,
paginateEntries,
viewInfo,
totalPages,
pages
}
}
}
</script>