Support for tables in frontend
This commit is contained in:
parent
e3fba930d2
commit
ee6dd16be2
@ -12,16 +12,17 @@ import alertContactsRoute_schema from './alertContacts_schema.js';
|
|||||||
// Router base is '/api/v1'
|
// Router base is '/api/v1'
|
||||||
const Router = express.Router({ strict: false });
|
const Router = express.Router({ strict: false });
|
||||||
|
|
||||||
// All empty strings are null values.
|
// All empty strings are null values (body)
|
||||||
Router.use('*', function (req, res, next) {
|
Router.use('*', function (req, res, next) {
|
||||||
for (let key in req.body) {
|
for (let key in req.body) {
|
||||||
if (req.body[key] === '') {
|
if (req.body[key] === '') {
|
||||||
req.body[key] = null;
|
req.body[key] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// All api routes lowercase! Yea I know but when strict: true it matters.
|
// All api routes lowercase! Yea I know but when strict: true it matters.
|
||||||
Router.route('/alertcontacts').get(alertContactsRoute.get).post(alertContactsRoute.post).patch(alertContactsRoute.patch).delete(alertContactsRoute.del);
|
Router.route('/alertcontacts').get(alertContactsRoute.get).post(alertContactsRoute.post).patch(alertContactsRoute.patch).delete(alertContactsRoute.del);
|
||||||
Router.route('/alertcontacts/describe').get(alertContactsRoute_schema);
|
Router.route('/alertcontacts/describe').get(alertContactsRoute_schema);
|
||||||
|
@ -21,12 +21,18 @@ let _api = {
|
|||||||
const response = await fetch(_apiConfig.basePath + path, options);
|
const response = await fetch(_apiConfig.basePath + path, options);
|
||||||
// Handle the response
|
// Handle the response
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
console.error('Failed to fetch:', response.statusText);
|
||||||
_testPageFail(response.statusText);
|
_testPageFail(response.statusText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
// Handle the result, was json valid?
|
// Handle the result, was json valid?
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
// Is it a number instead?
|
||||||
|
if (typeof result === 'number') {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
console.error('Invalid JSON response');
|
||||||
_testPageFail('Invalid JSON response');
|
_testPageFail('Invalid JSON response');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -53,15 +59,6 @@ let _api = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
|
||||||
getAsync: function (path, callback) {
|
|
||||||
const options = {
|
|
||||||
headers: new Headers({ 'content-type': 'application/json' })
|
|
||||||
};
|
|
||||||
fetch(_apiConfig.basePath + path, options)
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data) => callback(data))
|
|
||||||
.catch((error) => _testPageFail(error));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,21 +110,37 @@ function getApiDescriptionByTable(tableName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnTableDataByTableName(tableName) {
|
function returnTableDataByTableName(tableName, search="", orderBy="asc", sort="", take=-1, skip=0) {
|
||||||
return _api.get(tableName);
|
var orderBy = orderBy.toLowerCase();
|
||||||
|
if(orderBy == "") {
|
||||||
|
orderBy = "asc";
|
||||||
|
}
|
||||||
|
var baseString = tableName + "?order=" + orderBy;
|
||||||
|
if(sort && sort.length > 0) {
|
||||||
|
baseString += "&sort=" + sort;
|
||||||
|
}
|
||||||
|
if(take > 0) {
|
||||||
|
baseString += "&take=" + take;
|
||||||
|
}
|
||||||
|
if(skip > 0) {
|
||||||
|
baseString += "&skip=" + skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search && search.length > 0) {
|
||||||
|
return _api.get(baseString + '&search=' + search);
|
||||||
|
} else {
|
||||||
|
return _api.get(baseString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnTableDataByTableNameWithSearch(tableName, search) {
|
async function getCountByTable(tableName, search="") {
|
||||||
return _api.get(tableName + '?search=' + search);
|
let baseString = tableName + '?count=true';
|
||||||
}
|
if (search && search.length > 0) {
|
||||||
|
baseString += '&search=' + search;
|
||||||
function returnTableDataByTableNameAsync(tableName, callback) {
|
}
|
||||||
_api.getAsync(tableName, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getCountByTable(tableName) {
|
|
||||||
// Stored in `data:count:${tableName}`
|
// Stored in `data:count:${tableName}`
|
||||||
let result = await _api.get(tableName + '?count=true');
|
let result = await _api.get(baseString);
|
||||||
|
console.debug('Count result:', result);
|
||||||
if (typeof result !== 'number') {
|
if (typeof result !== 'number') {
|
||||||
_testPageWarn('Count was not a number, was: ' + result);
|
_testPageWarn('Count was not a number, was: ' + result);
|
||||||
console.warn('Count was not a number, was: ' + result);
|
console.warn('Count was not a number, was: ' + result);
|
||||||
@ -136,10 +149,6 @@ async function getCountByTable(tableName) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRowsByTableAndColumnList(tableName, columnList) {
|
|
||||||
//return _api.get(tableName + '/rows/' + columnList.join(','))
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _testPageFail(reason) {
|
function _testPageFail(reason) {
|
||||||
document.getElementById('heroStatus').classList.remove('is-success');
|
document.getElementById('heroStatus').classList.remove('is-success');
|
||||||
|
@ -26,45 +26,6 @@ var searchFields = document.querySelectorAll('input[data-searchTargetId]');
|
|||||||
// Find all modalForms
|
// Find all modalForms
|
||||||
var modalForms = document.querySelectorAll('form[data-targetTable]');
|
var modalForms = document.querySelectorAll('form[data-targetTable]');
|
||||||
|
|
||||||
// Iterate over all tables
|
|
||||||
tables.forEach(async (table) => {
|
|
||||||
console.log('Table found: ', table);
|
|
||||||
// Get THEAD and TBODY elements
|
|
||||||
const thead = table.querySelector('thead');
|
|
||||||
const tbody = table.querySelector('tbody');
|
|
||||||
|
|
||||||
// get index per column
|
|
||||||
const columns = thead.querySelectorAll('th');
|
|
||||||
const columnIndices = [];
|
|
||||||
columns.forEach((column, index) => {
|
|
||||||
columnIndices[column.getAttribute('data-dataCol')] = index;
|
|
||||||
});
|
|
||||||
|
|
||||||
// All required cols
|
|
||||||
let requiredCols = [];
|
|
||||||
columns.forEach((column) => {
|
|
||||||
requiredCols.push(column.getAttribute('data-dataCol'));
|
|
||||||
});
|
|
||||||
console.log('Required columns: ', requiredCols);
|
|
||||||
|
|
||||||
// Get data from API
|
|
||||||
//let result = getRowsByTableAndColumnList(table.getAttribute("data-dataSource"), requiredCols);
|
|
||||||
let result = await returnTableDataByTableName(table.getAttribute('data-dataSource'));
|
|
||||||
// for (resultIndex in result) {
|
|
||||||
// const row = result[resultIndex];
|
|
||||||
// const tr = document.createElement("tr");
|
|
||||||
// requiredCols.forEach(column => {
|
|
||||||
// const td = document.createElement("td");
|
|
||||||
// td.innerHTML = row[column];
|
|
||||||
// tr.appendChild(td);
|
|
||||||
// });
|
|
||||||
// tbody.appendChild(tr);
|
|
||||||
// }
|
|
||||||
writeDataToTable(table, result);
|
|
||||||
|
|
||||||
console.log('Column indices: ', columnIndices);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.info('Processing single values');
|
console.info('Processing single values');
|
||||||
console.info(singleValues);
|
console.info(singleValues);
|
||||||
|
|
||||||
@ -73,6 +34,42 @@ singleValues.forEach(async (singleValue) => {
|
|||||||
writeSingelton(singleValue);
|
writeSingelton(singleValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Iterate over all tables
|
||||||
|
tables.forEach(async (table) => {
|
||||||
|
// Get THs and attach onClick event to sort
|
||||||
|
const ths = table.querySelectorAll('th');
|
||||||
|
ths.forEach((th) => {
|
||||||
|
th.style.cursor = 'pointer';
|
||||||
|
th.style.userSelect = 'none';
|
||||||
|
th.addEventListener('click', async function () {
|
||||||
|
const table = th.closest('table');
|
||||||
|
const order = th.getAttribute('data-order');
|
||||||
|
// Clear all other order attributes
|
||||||
|
ths.forEach((th) => {
|
||||||
|
if (th != this) {
|
||||||
|
th.removeAttribute('data-order');
|
||||||
|
th.classList.remove("bi-caret-up-fill")
|
||||||
|
th.classList.remove("bi-caret-down-fill")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (order == 'ASC') {
|
||||||
|
th.setAttribute('data-order', 'DESC');
|
||||||
|
th.classList.add("bi-caret-down-fill")
|
||||||
|
th.classList.remove("bi-caret-up-fill")
|
||||||
|
} else {
|
||||||
|
th.setAttribute('data-order', 'ASC');
|
||||||
|
th.classList.add("bi-caret-up-fill")
|
||||||
|
th.classList.remove("bi-caret-down-fill")
|
||||||
|
}
|
||||||
|
refreshTable(table);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
refreshTable(table);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function writeSingelton(element) {
|
async function writeSingelton(element) {
|
||||||
const table = element.getAttribute('data-dataSource');
|
const table = element.getAttribute('data-dataSource');
|
||||||
console.log('Table: ', table, ' Action: ', element.getAttribute('data-dataAction'), ' Element: ', element);
|
console.log('Table: ', table, ' Action: ', element.getAttribute('data-dataAction'), ' Element: ', element);
|
||||||
@ -131,9 +128,6 @@ searchFields.forEach((searchField) => {
|
|||||||
const column = target.getAttribute('data-dataCol');
|
const column = target.getAttribute('data-dataCol');
|
||||||
const value = searchField.value;
|
const value = searchField.value;
|
||||||
console.log('Searching for ', value, ' in ', table, ' column ', column);
|
console.log('Searching for ', value, ' in ', table, ' column ', column);
|
||||||
//const result = await returnTableDataByTableNameWithSearch(table, value);
|
|
||||||
//clearTable(target);
|
|
||||||
//writeDataToTable(target, result);
|
|
||||||
refreshTableByName(table);
|
refreshTableByName(table);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -152,6 +146,8 @@ modalForms.forEach((modalForm) => {
|
|||||||
event.target.classList.remove('is-danger');
|
event.target.classList.remove('is-danger');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
getApiDescriptionByTable(modalForm.getAttribute('data-targetTable')).then((desc) => {
|
getApiDescriptionByTable(modalForm.getAttribute('data-targetTable')).then((desc) => {
|
||||||
console.log('Description: ', desc);
|
console.log('Description: ', desc);
|
||||||
const keys = desc['POST']['keys'];
|
const keys = desc['POST']['keys'];
|
||||||
@ -246,9 +242,6 @@ modalForms.forEach((modalForm) => {
|
|||||||
// TODO: Show error message
|
// TODO: Show error message
|
||||||
}
|
}
|
||||||
|
|
||||||
// const target = document.getElementById(table);
|
|
||||||
// writeDataToTable(target, result);
|
|
||||||
|
|
||||||
// Find all tables with data-searchTargetId set to table
|
// Find all tables with data-searchTargetId set to table
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
refreshTableByName(table);
|
refreshTableByName(table);
|
||||||
@ -261,19 +254,73 @@ modalForms.forEach((modalForm) => {
|
|||||||
async function refreshTable(table) {
|
async function refreshTable(table) {
|
||||||
// Refresh a table while keeping (optionally set) search value
|
// Refresh a table while keeping (optionally set) search value
|
||||||
const searchField = document.querySelector("input[data-searchTargetId='" + table.id + "']");
|
const searchField = document.querySelector("input[data-searchTargetId='" + table.id + "']");
|
||||||
if (searchField && searchField.value != '') {
|
// Get state of order and sort
|
||||||
|
const ths = table.querySelectorAll('th');
|
||||||
|
const columnIndices = [];
|
||||||
|
ths.forEach((th, index) => {
|
||||||
|
columnIndices[th.getAttribute('data-dataCol')] = index;
|
||||||
|
});
|
||||||
|
let order = '';
|
||||||
|
let column = '';
|
||||||
|
ths.forEach((th) => {
|
||||||
|
if (th.hasAttribute('data-order')) {
|
||||||
|
order = th.getAttribute('data-order');
|
||||||
|
column = th.getAttribute('data-dataCol');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('Order: ', order, ' Column: ', column);
|
||||||
|
|
||||||
|
const maxLinesPerPage = table.getAttribute('data-pageSize');
|
||||||
|
let currentPage = table.getAttribute('data-currentPage');
|
||||||
|
if(currentPage == null) {
|
||||||
|
table.setAttribute('data-currentPage', 1);
|
||||||
|
currentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = (currentPage - 1) * maxLinesPerPage;
|
||||||
|
const end = start + maxLinesPerPage;
|
||||||
|
|
||||||
|
let paginationPassOnPre = {
|
||||||
|
'start': start,
|
||||||
|
'end': end,
|
||||||
|
'currentPage': currentPage,
|
||||||
|
'maxLinesPerPage': maxLinesPerPage
|
||||||
|
};
|
||||||
|
|
||||||
|
if (searchField) {
|
||||||
const value = searchField.value;
|
const value = searchField.value;
|
||||||
const dbTable = table.getAttribute('data-dataSource');
|
const dbTable = table.getAttribute('data-dataSource');
|
||||||
const result = await returnTableDataByTableNameWithSearch(dbTable, value);
|
const result = await returnTableDataByTableName(dbTable, value, order, column, take= maxLinesPerPage, skip= start);
|
||||||
|
const totalResultCount = await getCountByTable(dbTable, value);
|
||||||
|
paginationPassOnPre['dataLength'] = totalResultCount;
|
||||||
|
var magMiddl = managePaginationMiddleware(result, paginationPassOnPre);
|
||||||
|
var data = magMiddl[0];
|
||||||
|
var paginationPassOn = magMiddl[1];
|
||||||
clearTable(table);
|
clearTable(table);
|
||||||
writeDataToTable(table, result);
|
writeDataToTable(table, data, paginationPassOn);
|
||||||
} else {
|
} else {
|
||||||
const result = await returnTableDataByTableName(table.getAttribute('data-dataSource'));
|
const result = await returnTableDataByTableName(table.getAttribute('data-dataSource'), undefined, order, column, take= maxLinesPerPage, skip= start);
|
||||||
|
const resultCount = await getCountByTable(table.getAttribute('data-dataSource'));
|
||||||
|
paginationPassOnPre['dataLength'] = resultCount;
|
||||||
|
var magMiddl = managePaginationMiddleware(result, paginationPassOnPre);
|
||||||
|
var data = magMiddl[0];
|
||||||
|
var paginationPassOn = magMiddl[1];
|
||||||
clearTable(table);
|
clearTable(table);
|
||||||
writeDataToTable(table, result);
|
writeDataToTable(table, data, paginationPassOn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function managePaginationMiddleware(data, paginationPassOnPre) {
|
||||||
|
const maxLinesPerPage = paginationPassOnPre['maxLinesPerPage'];
|
||||||
|
|
||||||
|
const dataLength = paginationPassOnPre['dataLength'];
|
||||||
|
const maxPages = Math.ceil(dataLength / maxLinesPerPage);
|
||||||
|
paginationPassOn = paginationPassOnPre;
|
||||||
|
paginationPassOn['maxPages'] = maxPages;
|
||||||
|
// paginationPassOn['dataLength'] = dataLength;
|
||||||
|
return [data, paginationPassOn];
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshTableByName(name) {
|
async function refreshTableByName(name) {
|
||||||
const dirtyTables = document.querySelectorAll("table[data-dataSource='" + name + "']");
|
const dirtyTables = document.querySelectorAll("table[data-dataSource='" + name + "']");
|
||||||
for (dirty of dirtyTables) {
|
for (dirty of dirtyTables) {
|
||||||
@ -293,7 +340,10 @@ function clearTable(table) {
|
|||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeDataToTable(table, data) {
|
function writeDataToTable(table, data, paginationPassOn) {
|
||||||
|
if(data == undefined || data == null || data.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log('Writing data to table: ', table, data);
|
console.log('Writing data to table: ', table, data);
|
||||||
// Get THEAD and TBODY elements
|
// Get THEAD and TBODY elements
|
||||||
const thead = table.querySelector('thead');
|
const thead = table.querySelector('thead');
|
||||||
@ -312,18 +362,106 @@ function writeDataToTable(table, data) {
|
|||||||
requiredCols.push(column.getAttribute('data-dataCol'));
|
requiredCols.push(column.getAttribute('data-dataCol'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get paginationPassOn
|
||||||
|
const start = paginationPassOn['start'];
|
||||||
|
const end = paginationPassOn['end'];
|
||||||
|
const currentPage = paginationPassOn['currentPage'];
|
||||||
|
const maxLinesPerPage = paginationPassOn['maxLinesPerPage'];
|
||||||
|
const maxPages = paginationPassOn['maxPages'];
|
||||||
|
const dataLength = paginationPassOn['dataLength'];
|
||||||
|
|
||||||
|
|
||||||
|
// Find nav with class pagination and data-targetTable="table.id"
|
||||||
|
const paginationElement = document.querySelector("nav.pagination[data-targetTable='" + table.id + "']");
|
||||||
|
const paginationList = paginationElement.querySelector('ul.pagination-list');
|
||||||
|
console.log('Data length: ', dataLength, ' Max pages: ', maxPages);
|
||||||
|
|
||||||
|
if(maxPages > 1) {
|
||||||
|
// Clear pagination list
|
||||||
|
paginationList.innerHTML = '';
|
||||||
|
|
||||||
|
for (let i = 1; i <= maxPages; i++) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = '<a class="pagination-link" aria-label="Goto page ' + i + '" data-page="' + i + '">' + i + '</a>';
|
||||||
|
if(i == currentPage) {
|
||||||
|
li.querySelector('a').classList.add('is-current');
|
||||||
|
}
|
||||||
|
paginationList.appendChild(li);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Remove unused pages, only leave first, last, current and 2 neighbors
|
||||||
|
let pages = paginationList.querySelectorAll('li');
|
||||||
|
let friends = []
|
||||||
|
// Always add first and last
|
||||||
|
friends.push(0);
|
||||||
|
friends.push(pages.length - 1);
|
||||||
|
friends.push(currentPage-1);
|
||||||
|
|
||||||
|
// Add direct neighbors
|
||||||
|
// friends.push(currentPage - 2);
|
||||||
|
friends.push(currentPage);
|
||||||
|
friends.push(currentPage - 2);
|
||||||
|
|
||||||
|
|
||||||
|
// Deduplicate friends
|
||||||
|
friends = [...new Set(friends)];
|
||||||
|
// Sort friends
|
||||||
|
friends.sort((a, b) => a - b);
|
||||||
|
// Parse friends (string to int)
|
||||||
|
friends = friends.map((x) => parseInt(x));
|
||||||
|
|
||||||
|
console.log('Friends: ', friends, ' Pages: ', pages.length, ' Current: ', currentPage);
|
||||||
|
|
||||||
|
|
||||||
|
// Remove everyone who is not a friend
|
||||||
|
for(let i = 0; i < pages.length; i++) {
|
||||||
|
if(friends.includes(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pages[i].remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all gaps (step size bigger then 1) and add an ellipsis in between the two numbers
|
||||||
|
let last = 0;
|
||||||
|
for(let i = 0; i < friends.length; i++) {
|
||||||
|
if(friends[i] - last > 1) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = '<span class="pagination-ellipsis">…</span>';
|
||||||
|
paginationList.insertBefore(li, pages[friends[i]]);
|
||||||
|
}
|
||||||
|
last = friends[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Append on click event to all pagination links
|
||||||
|
paginationList.querySelectorAll('a').forEach((link) => {
|
||||||
|
link.addEventListener('click', async function() {
|
||||||
|
const page = link.getAttribute('data-page');
|
||||||
|
table.setAttribute('data-currentPage', page);
|
||||||
|
refreshTable(table);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
paginationElement.classList.remove('is-hidden');
|
||||||
|
} else {
|
||||||
|
paginationElement.classList.add('is-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for (resultIndex in data) {
|
for (resultIndex in data) {
|
||||||
const row = data[resultIndex];
|
const row = data[resultIndex];
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
requiredCols.forEach((column) => {
|
requiredCols.forEach((column) => {
|
||||||
const td = document.createElement('td');
|
const td = document.createElement('td');
|
||||||
td.innerHTML = row[column];
|
td.innerText = row[column];
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
});
|
});
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Handle modal
|
// Handle modal
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Functions to open and close a modal
|
// Functions to open and close a modal
|
||||||
|
@ -101,7 +101,7 @@
|
|||||||
<section class="section">
|
<section class="section">
|
||||||
<h1 class="title" data-tK="start-recent-header">Alarm Kontakte</h1>
|
<h1 class="title" data-tK="start-recent-header">Alarm Kontakte</h1>
|
||||||
<input class="input" type="text" data-searchTargetId="contactTable" placeholder="Search..." />
|
<input class="input" type="text" data-searchTargetId="contactTable" placeholder="Search..." />
|
||||||
<table class="table is-striped is-fullwidth" data-dataSource="AlertContacts" id="contactTable">
|
<table class="table is-striped is-fullwidth is-hoverable" data-dataSource="AlertContacts" id="contactTable" data-pageSize="5">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th data-dataCol = "id"><abbr title="Position">Pos</abbr></th>
|
<th data-dataCol = "id"><abbr title="Position">Pos</abbr></th>
|
||||||
@ -112,7 +112,13 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
<nav class="pagination is-hidden" role="navigation" aria-label="pagination" data-targetTable="contactTable">
|
||||||
|
<ul class="pagination-list">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<%~ include("partials/footer.eta") %>
|
<%~ include("partials/footer.eta") %>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user