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 >
<div >
<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 >
<label>
Filter:
<input type="search" v-model="searchInput" placeholder="" @keyup="search">
</label>
</div>
<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" >
<td :colspan="headers.length">No Data</td>
</tr>
</tbody>
</table>
<div 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 id="DataTables_Table_0_paginate">
<a : @click="paginateEntries('previous', page)">Previous</a>
<span v-for="page in pages" :key="page">
<a : @click="paginateEntries('page', page)">{{page}}</a>
</span>
<a : @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>
CodePudding user response:
Making the table reactive
In two instances you're using ref
objects when you should be using computed
. Basically, anything that should automatically "recompute" based on a change to a dependency (tableData) should be a computed property:
In your datatable component:
let entries = computed(() => [...props.rows]);
let filteredRows = computed(() =>
dataTable.paginate(entries.value, 1, entriesToDisplay.value)
);
In the parent vue page:
Here you've created tableRows
as a standard array when it should actually be a ref
array:
let tableRows = ref([]);
and then there's one line you need to update to use .value
:
const buildTableRows = () => {
...
tableRows.value.push(row);
}
Now you should notice new entries being added to the table when pressing the Add button
Creating working Edit button
First, I noticed in buildTableRows
you're calling a function buildRow()
in your data-table.js file that expects an id, but you forgot to pass that in, so here's that fix:
var row = dataTable.buildRow(tableData.value[i].id);
Then in the same function, creating the edit button using buildLink
expects a function as the fourth parameter which you should do by just providing the function name (passing in parameters will be handled in the datatable component):
dataTable.buildLink(colThree, 'Edit', '', editItem);
In that datatable component is where you would pass in your row.id parameter (which is now no longer undefined thanks to the fix above).
<li v-for="(link, index) in column.links" :key="index">
<a @click="link.click(row.id)">{{ link.text }}</a>
</li>
Now when you click the Edit button in your data table you will see your editItem
function console logging the row id. With the id, you can retrieve that row from tableData
and do with it what you want... Setting up the delete button would be the same way!