621 lines
19 KiB
JavaScript
621 lines
19 KiB
JavaScript
|
_pageDriverVersion = '1.0.1';
|
||
|
|
||
|
// Handle color for icon svg with id="logo" based on the current theme
|
||
|
const logo = document.getElementById('logo');
|
||
|
if (logo) {
|
||
|
logo.style.fill = getComputedStyle(document.documentElement).getPropertyValue('--bulma-text');
|
||
|
}
|
||
|
|
||
|
if (_wrapperVersion === undefined) {
|
||
|
console.error('API Wrapper not found; Please include the API Wrapper before including the Page Driver');
|
||
|
exit();
|
||
|
} else {
|
||
|
console.log('API Wrapper found; Page Driver is ready to use');
|
||
|
}
|
||
|
|
||
|
// Find all tables on the page which have data-dataSource attribute
|
||
|
var tables = document.querySelectorAll('table[data-dataSource]');
|
||
|
//var tables = []
|
||
|
|
||
|
// Get all single values with data-dataSource, data-dataCol and data-dataAction
|
||
|
var singleValues = document.querySelectorAll('span[data-dataSource]');
|
||
|
|
||
|
// Find all search fields with data-searchTargetId
|
||
|
var searchFields = document.querySelectorAll('input[data-searchTargetId]');
|
||
|
|
||
|
// Find all modalForms
|
||
|
var modalForms = document.querySelectorAll('form[data-targetTable]');
|
||
|
|
||
|
console.info('Processing single values');
|
||
|
console.info(singleValues);
|
||
|
|
||
|
// Iterate over all single values
|
||
|
singleValues.forEach(async (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) => {
|
||
|
if(th.getAttribute('fnc') == "actions") {
|
||
|
return;
|
||
|
}
|
||
|
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) {
|
||
|
const table = element.getAttribute('data-dataSource');
|
||
|
console.log('Table: ', table, ' Action: ', element.getAttribute('data-dataAction'), ' Element: ', element);
|
||
|
switch (element.getAttribute('data-dataAction')) {
|
||
|
case 'COUNT': {
|
||
|
console.log('Count action found');
|
||
|
element.innerHTML = await getCountByTable(table);
|
||
|
break;
|
||
|
}
|
||
|
case 'SPECIAL': {
|
||
|
if (table == 'version') {
|
||
|
element.innerHTML = (await getServerVersion())['version'];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
default: {
|
||
|
console.error('Unknown action found: ', element.getAttribute('data-dataAction'));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
element.classList.remove('is-skeleton');
|
||
|
}
|
||
|
|
||
|
// Attach listeners to search fields
|
||
|
searchFields.forEach((searchField) => {
|
||
|
// Apply restrictions to search field (min, max, chars, etc)
|
||
|
|
||
|
getApiDescriptionByTable(document.getElementById(searchField.getAttribute('data-searchTargetId')).getAttribute('data-dataSource')).then((desc) => {
|
||
|
desc = desc['GET']['keys']['search'];
|
||
|
var rules = desc['rules'];
|
||
|
rules.forEach((rule) => {
|
||
|
switch (rule['name']) {
|
||
|
case 'min': {
|
||
|
searchField.setAttribute('minlength', rule['args']['limit']);
|
||
|
break;
|
||
|
}
|
||
|
case 'max': {
|
||
|
searchField.setAttribute('maxlength', rule['args']['limit']);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
searchField.addEventListener('input', async function () {
|
||
|
console.log('Search field changed: ', searchField);
|
||
|
if (searchField.checkValidity() == false) {
|
||
|
console.log('Invalid input');
|
||
|
searchField.classList.add('is-danger');
|
||
|
return;
|
||
|
} else {
|
||
|
searchField.classList.remove('is-danger');
|
||
|
const targetId = searchField.getAttribute('data-searchTargetId');
|
||
|
const target = document.getElementById(targetId);
|
||
|
const table = target.getAttribute('data-dataSource');
|
||
|
const column = target.getAttribute('data-dataCol');
|
||
|
const value = searchField.value;
|
||
|
console.log('Searching for ', value, ' in ', table, ' column ', column);
|
||
|
refreshTableByName(table);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Attach listeners to modal forms
|
||
|
modalForms.forEach((modalForm) => {
|
||
|
// Add validation to form by using API description (everything is assumed POST for now)
|
||
|
modalForm.addEventListener('input', async function (event) {
|
||
|
if (event.target.checkValidity() == false) {
|
||
|
modalForm.querySelector("input[type='submit']").setAttribute('disabled', true);
|
||
|
event.target.classList.add('is-danger');
|
||
|
return;
|
||
|
} else {
|
||
|
modalForm.querySelector("input[type='submit']").removeAttribute('disabled');
|
||
|
event.target.classList.remove('is-danger');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
|
||
|
getApiDescriptionByTable(modalForm.getAttribute('data-targetTable')).then((desc) => {
|
||
|
console.log('Description: ', desc);
|
||
|
const keys = desc['POST']['keys'];
|
||
|
// Apply resitrictions and types to form fields
|
||
|
for (key in keys) {
|
||
|
const field = modalForm.querySelector("input[name='" + key + "']");
|
||
|
if (field) {
|
||
|
const rules = keys[key]['rules'];
|
||
|
const flags = keys[key]['flags'];
|
||
|
console.log('Field: ', field, ' Rules: ', rules, ' Flags: ', flags);
|
||
|
rules.forEach((rule) => {
|
||
|
switch (rule['name']) {
|
||
|
case 'min': {
|
||
|
field.setAttribute('minlength', rule['args']['limit']);
|
||
|
break;
|
||
|
}
|
||
|
case 'max': {
|
||
|
field.setAttribute('maxlength', rule['args']['limit']);
|
||
|
break;
|
||
|
}
|
||
|
case 'pattern': {
|
||
|
field.setAttribute('pattern', rule['args']['regex'].substring(1, rule['args']['regex'].length - 1));
|
||
|
//field.setAttribute("pattern", "^[\\+]?[\\(]?[0-9]{3}[\\)]?[\\-\\s\\.]?[0-9]{3}[\\-\\s\\.]?[0-9]{4,9}$");
|
||
|
break;
|
||
|
}
|
||
|
case 'type': {
|
||
|
//field.setAttribute("type", rule["args"]["type"]);
|
||
|
console.log('Type: ', rule['args']['type']);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
if (flags) {
|
||
|
flags['presence'] == 'required' ? field.setAttribute('required', true) : field.removeAttribute('required');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
console.log('Keys: ', keys);
|
||
|
});
|
||
|
|
||
|
modalForm.addEventListener('submit', async function (event) {
|
||
|
event.preventDefault();
|
||
|
// Check what button submitted the form and if it has data-actionBtn = save
|
||
|
// If not, close modal
|
||
|
const pressedBtn = event.submitter;
|
||
|
if (pressedBtn.getAttribute('data-actionBtn') != 'save') {
|
||
|
modalForm.closest('.modal').classList.remove('is-active');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Find .entryPhase and hide it
|
||
|
const entryPhase = modalForm.querySelector('.entryPhase');
|
||
|
const loadPhase = modalForm.querySelector('.loadPhase');
|
||
|
if (entryPhase) {
|
||
|
entryPhase.classList.add('is-hidden');
|
||
|
}
|
||
|
if (loadPhase) {
|
||
|
loadPhase.classList.remove('is-hidden');
|
||
|
}
|
||
|
console.log('Form submitted: ', modalForm);
|
||
|
const table = modalForm.getAttribute('data-targetTable');
|
||
|
const data = new FormData(modalForm);
|
||
|
// Convert to JSON object
|
||
|
let jsonData = {};
|
||
|
data.forEach((value, key) => {
|
||
|
jsonData[key] = value;
|
||
|
});
|
||
|
console.log('JSON Data: ', jsonData);
|
||
|
let resp = {};
|
||
|
if(modalForm.getAttribute('data-action') == 'edit') {
|
||
|
Rid = modalForm.getAttribute('data-rid');
|
||
|
resp = await updateRow(table, Rid,jsonData);
|
||
|
modalForm.setAttribute('data-action', 'create');
|
||
|
} else {
|
||
|
resp = await createEntry(table, jsonData);
|
||
|
}
|
||
|
|
||
|
console.log('Response: ', resp);
|
||
|
if (resp['status'] == 'CREATED' || resp['status'] == 'UPDATED') {
|
||
|
console.log('Entry created successfully');
|
||
|
modalForm.closest('.modal').classList.remove('is-active');
|
||
|
modalForm.reset();
|
||
|
// Hide loadPhase
|
||
|
if (loadPhase) {
|
||
|
loadPhase.classList.add('is-hidden');
|
||
|
}
|
||
|
// Show entryPhase
|
||
|
if (entryPhase) {
|
||
|
entryPhase.classList.remove('is-hidden');
|
||
|
}
|
||
|
} else {
|
||
|
// Hide loadPhase
|
||
|
if (loadPhase) {
|
||
|
loadPhase.classList.add('is-hidden');
|
||
|
}
|
||
|
// Show entryPhase
|
||
|
if (entryPhase) {
|
||
|
entryPhase.classList.remove('is-hidden');
|
||
|
}
|
||
|
// TODO: Show error message
|
||
|
}
|
||
|
|
||
|
// Find all tables with data-searchTargetId set to table
|
||
|
setTimeout(() => {
|
||
|
refreshTableByName(table);
|
||
|
updateSingeltonsByTableName(table);
|
||
|
}, 500);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Helper
|
||
|
async function refreshTable(table) {
|
||
|
// Refresh a table while keeping (optionally set) search value
|
||
|
const searchField = document.querySelector("input[data-searchTargetId='" + table.id + "']");
|
||
|
// 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 dbTable = table.getAttribute('data-dataSource');
|
||
|
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);
|
||
|
writeDataToTable(table, data, paginationPassOn);
|
||
|
} else {
|
||
|
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);
|
||
|
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) {
|
||
|
const dirtyTables = document.querySelectorAll("table[data-dataSource='" + name + "']");
|
||
|
for (dirty of dirtyTables) {
|
||
|
refreshTable(dirty);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function updateSingeltonsByTableName(name) {
|
||
|
const dirtySingles = document.querySelectorAll("span[data-dataSource='" + name + "']");
|
||
|
for (dirty of dirtySingles) {
|
||
|
writeSingelton(dirty);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function clearTable(table) {
|
||
|
const tbody = table.querySelector('tbody');
|
||
|
tbody.innerHTML = '';
|
||
|
}
|
||
|
|
||
|
function writeDataToTable(table, data, paginationPassOn) {
|
||
|
if(data == undefined || data == null || data.length == 0) {
|
||
|
return;
|
||
|
}
|
||
|
console.log('Writing data to table: ', table, data);
|
||
|
// 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 = [];
|
||
|
let actionFields = [];
|
||
|
columns.forEach((column) => {
|
||
|
// console.log('Column: ', column, ' FNC: ', column.getAttribute('data-fnc'), column.attributes);
|
||
|
if(column.getAttribute('data-fnc') == "actions") {
|
||
|
console.log('!!! Found actions column !!!');
|
||
|
actionFields.push(column);
|
||
|
return;
|
||
|
}
|
||
|
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) {
|
||
|
const row = data[resultIndex];
|
||
|
const tr = document.createElement('tr');
|
||
|
requiredCols.forEach((column) => {
|
||
|
const td = document.createElement('td');
|
||
|
td.innerText = row[column];
|
||
|
tr.appendChild(td);
|
||
|
});
|
||
|
|
||
|
// Add action fields
|
||
|
actionFields.forEach((actionField) => {
|
||
|
const td = document.createElement('td');
|
||
|
const actions = actionField.getAttribute('data-actions').split(',');
|
||
|
actions.forEach((action) => {
|
||
|
const button = document.createElement('button');
|
||
|
let icon = '';
|
||
|
let color = 'is-primary';
|
||
|
switch(action) {
|
||
|
case 'edit': {
|
||
|
icon = '<i class="bi bi-pencil"></i>';
|
||
|
break;
|
||
|
}
|
||
|
case 'delete': {
|
||
|
icon = '<i class="bi bi-trash"></i>';
|
||
|
color = 'is-danger';
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
// Add classes
|
||
|
button.classList.add('button');
|
||
|
button.classList.add('is-small');
|
||
|
button.classList.add(color);
|
||
|
button.classList.add('is-outlined');
|
||
|
button.innerHTML = ` <span class="icon is-small">${icon}</span> `;
|
||
|
button.style.marginRight = '5px';
|
||
|
|
||
|
// Add data-action and data-id
|
||
|
button.setAttribute('data-action', action);
|
||
|
button.setAttribute("data-id", row["id"]);
|
||
|
|
||
|
// Add event listener
|
||
|
button.addEventListener('click', async function() {
|
||
|
const table = actionField.closest('table');
|
||
|
const row = button.closest('tr');
|
||
|
const columns = table.querySelectorAll('th');
|
||
|
const columnIndices = [];
|
||
|
columns.forEach((column, index) => {
|
||
|
columnIndices[column.getAttribute('data-dataCol')] = index;
|
||
|
});
|
||
|
const data = [];
|
||
|
columns.forEach((column) => {
|
||
|
data[column.getAttribute('data-dataCol')] = row.children[columnIndices[column.getAttribute('data-dataCol')]].innerText;
|
||
|
});
|
||
|
console.log('Data: ', data);
|
||
|
switch(action) {
|
||
|
case 'edit': {
|
||
|
// Open modal with form
|
||
|
const form = document.querySelector("form[data-targetTable='" + table.getAttribute('data-dataSource') + "']");
|
||
|
const formTitle = form.querySelector('.title');
|
||
|
const entryPhase = form.querySelector('.entryPhase');
|
||
|
const loadPhase = form.querySelector('.loadPhase');
|
||
|
const fields = form.querySelectorAll('input');
|
||
|
// Set modal to edit mode
|
||
|
form.setAttribute('data-action', 'edit');
|
||
|
form.setAttribute('data-rid', button.getAttribute('data-id'));
|
||
|
formTitle.innerText = 'Edit entry';
|
||
|
fields.forEach((field) => {
|
||
|
// Skip for submit button
|
||
|
if(field.getAttribute('type') == 'submit') {
|
||
|
return;
|
||
|
}
|
||
|
field.value = data[field.getAttribute('name')];
|
||
|
});
|
||
|
form.closest('.modal').classList.add('is-active');
|
||
|
// TBD
|
||
|
break;
|
||
|
}
|
||
|
case 'delete': {
|
||
|
// confirm
|
||
|
const confirm = window.confirm('Do you really want to delete this entry?');
|
||
|
// Delete entry
|
||
|
if(confirm) {
|
||
|
const table = actionField.closest('table');
|
||
|
const id = button.getAttribute('data-id');
|
||
|
const resp = await deleteRow(table.getAttribute('data-dataSource'), id);
|
||
|
if(resp['status'] == 'DELETED') {
|
||
|
refreshTable(table);
|
||
|
updateSingeltonsByTableName(table.getAttribute('data-dataSource'));
|
||
|
} else {
|
||
|
// Show error message
|
||
|
// TODO: Show error message
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
td.appendChild(button);
|
||
|
});
|
||
|
tr.appendChild(td);
|
||
|
});
|
||
|
tbody.appendChild(tr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Handle modal
|
||
|
document.addEventListener('DOMContentLoaded', () => {
|
||
|
// Functions to open and close a modal
|
||
|
function openModal($el) {
|
||
|
$el.classList.add('is-active');
|
||
|
}
|
||
|
|
||
|
function closeModal($el) {
|
||
|
$el.classList.remove('is-active');
|
||
|
}
|
||
|
|
||
|
function closeAllModals() {
|
||
|
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||
|
closeModal($modal);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Add a click event on buttons to open a specific modal
|
||
|
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||
|
const modal = $trigger.dataset.target;
|
||
|
const $target = document.getElementById(modal);
|
||
|
|
||
|
$trigger.addEventListener('click', () => {
|
||
|
openModal($target);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Add a click event on various child elements to close the parent modal
|
||
|
(document.querySelectorAll('.modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
|
||
|
const $target = $close.closest('.modal');
|
||
|
|
||
|
$close.addEventListener('click', () => {
|
||
|
closeModal($target);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Add a keyboard event to close all modals
|
||
|
document.addEventListener('keydown', (event) => {
|
||
|
if (event.key === 'Escape') {
|
||
|
closeAllModals();
|
||
|
}
|
||
|
});
|
||
|
});
|