Current state
This commit is contained in:
109
static/apiWrapper.js
Normal file
109
static/apiWrapper.js
Normal file
@ -0,0 +1,109 @@
|
||||
_wrapperVersion = "1.0.0"
|
||||
_minApiVersion = "1.0.0"
|
||||
_maxApiVersion = "1.0.0"
|
||||
|
||||
_apiConfig = {
|
||||
"basePath": "/api/v1/"
|
||||
}
|
||||
|
||||
// Generic driver functions
|
||||
let _api = {
|
||||
"get": async function(path) {
|
||||
const options = {
|
||||
headers: new Headers({'content-type': 'application/json'})
|
||||
};
|
||||
const response = await fetch(_apiConfig.basePath + path, options)
|
||||
// Handle the response
|
||||
if (!response.ok) {
|
||||
_testPageFail(response.statusText)
|
||||
return
|
||||
}
|
||||
const result = await response.json()
|
||||
// Handle the result, was json valid?
|
||||
if (!result) {
|
||||
_testPageFail("Invalid JSON response")
|
||||
return
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
"post": async function(path, data) {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: new Headers({'content-type': 'application/json'}),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
const response = await fetch(_apiConfig.basePath + path, options)
|
||||
// Handle the response
|
||||
if (!response.ok) {
|
||||
_testPageFail(response.statusText)
|
||||
return
|
||||
}
|
||||
const result = await response.json()
|
||||
// Handle the result, was json valid?
|
||||
if (!result) {
|
||||
_testPageFail("Invalid JSON response")
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function returnTableDataByTableName(tableName) {
|
||||
return _api.get(tableName)
|
||||
}
|
||||
|
||||
function returnTableDataByTableNameWithSearch(tableName, search) {
|
||||
return _api.get(tableName + "?search=" + search)
|
||||
}
|
||||
|
||||
function returnTableDataByTableNameAsync(tableName, callback) {
|
||||
_api.getAsync(tableName, callback)
|
||||
}
|
||||
|
||||
async function getCountByTable(tableName) {
|
||||
let result = await(_api.get(tableName + "?count"))
|
||||
if(typeof result !== 'number') {
|
||||
_testPageWarn("Count was not a number, was: " + result)
|
||||
console.warn("Count was not a number, was: " + result)
|
||||
return -1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getRowsByTableAndColumnList(tableName, columnList) {
|
||||
//return _api.get(tableName + '/rows/' + columnList.join(','))
|
||||
return undefined
|
||||
}
|
||||
|
||||
function _testPageFail(reason) {
|
||||
document.getElementById("heroStatus").classList.remove("is-success")
|
||||
document.getElementById("heroStatus").classList.add("is-danger")
|
||||
|
||||
document.getElementById("heroExplainer").innerHTML = "API Wrapper Test Failed, reason: " + reason
|
||||
}
|
||||
|
||||
function _testPageWarn(reason) {
|
||||
document.getElementById("heroStatus").classList.remove("is-success")
|
||||
document.getElementById("heroStatus").classList.add("is-warning")
|
||||
|
||||
document.getElementById("heroExplainer").innerHTML = "API Wrapper Test Warning, reason: " + reason
|
||||
}
|
||||
|
||||
function getServerVersion() {
|
||||
return _api.get('version')
|
||||
}
|
||||
|
||||
function createEntry(tableName, data) {
|
||||
return _api.post(tableName, data)
|
||||
}
|
||||
|
3
static/favicon.svg
Normal file
3
static/favicon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg id="favicon" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="138.654" height="146.519" viewBox="0 0 36.685 38.767">
|
||||
<path d="M18.775 0A24.388 24.388 0 0 0 6.82 3.115C3.15 5.165-1.91 9.252.736 13.985c.37.66.9 1.221 1.47 1.713 1.532 1.322 2.98.222 4.554-.457.975-.42 1.95-.842 2.922-1.27.434-.19 1.01-.33 1.328-.698.858-.99.494-2.994.05-4.095a27.25 27.25 0 0 1 3.65-1.24v30.828h7.215V7.671c1.05.184 2.438.432 3.266 1.041.387.284.113.908.076 1.297-.08.827-.027 1.817.344 2.581.308.632 1.16.784 1.765 1.008l4.564 1.704c.628.232 1.33.643 1.979.297 2.822-1.507 3.574-5.39 1.843-8.023-1.165-1.77-3.255-3.13-5.035-4.216C27.037 1.107 22.906.014 18.775 0z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 678 B |
3
static/logo.svg
Normal file
3
static/logo.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg id="logo" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="498.424" height="148.888" viewBox="0 0 131.875 39.393">
|
||||
<path d="M118.368 51.177c-3.682 0-6.537.32-8.566.958-1.99.6-3.419 1.635-4.283 3.099-.827 1.466-1.239 3.533-1.239 6.2 0 2.03.356 3.7 1.07 5.016.714 1.315 1.916 2.46 3.607 3.438 1.728.939 4.17 1.878 7.326 2.817 2.517.752 4.452 1.466 5.805 2.142 1.39.64 2.386 1.352 2.987 2.142.601.79.9 1.747.9 2.874 0 1.24-.224 2.198-.675 2.874-.451.64-1.202 1.09-2.254 1.353-1.052.263-2.536.375-4.452.338-1.916-.038-3.4-.226-4.453-.564-1.051-.376-1.822-.977-2.31-1.804-.451-.826-.733-2.01-.845-3.55h-7.045c-.113 3.157.263 5.598 1.127 7.327.864 1.728 2.348 2.95 4.452 3.663 2.142.714 5.166 1.07 9.074 1.07 3.795 0 6.706-.318 8.735-.958 2.066-.638 3.532-1.728 4.396-3.268.864-1.54 1.296-3.72 1.296-6.538 0-2.254-.357-4.095-1.07-5.522-.715-1.466-1.917-2.706-3.608-3.72-1.653-1.015-4.02-2.01-7.1-2.987-2.518-.79-4.49-1.485-5.918-2.085-1.39-.6-2.404-1.202-3.043-1.804-.639-.6-.959-1.277-.959-2.028 0-1.165.207-2.049.62-2.649.414-.601 1.09-1.033 2.03-1.296.976-.263 2.366-.395 4.17-.395 1.728 0 3.061.15 4.001.45.977.264 1.672.734 2.085 1.41.451.638.733 1.578.846 2.818h7.157c.038-2.856-.376-5.054-1.24-6.594-.863-1.54-2.292-2.63-4.283-3.27-1.954-.637-4.734-.957-8.34-.957zm-67.058.12a24.388 24.388 0 0 0-11.954 3.114c-3.67 2.051-8.73 6.137-6.085 10.87.37.66.9 1.222 1.47 1.714 1.53 1.322 2.98.222 4.554-.458.975-.42 1.95-.842 2.922-1.268.433-.19 1.01-.331 1.328-.7.858-.99.494-2.994.05-4.094a27.22 27.22 0 0 1 3.651-1.24v30.828h7.214V58.968c1.05.182 2.439.43 3.266 1.04.387.285.113.91.075 1.298-.08.827-.027 1.816.345 2.58.307.632 1.16.785 1.765 1.009l4.564 1.703c.628.233 1.33.644 1.979.298 2.822-1.508 3.574-5.39 1.842-8.023-1.164-1.771-3.254-3.13-5.034-4.216-3.69-2.254-7.822-3.347-11.952-3.36zm-39.287.443L1.146 90.063h7.045l2.423-8.453h12.962l2.48 8.453h7.101L22.055 51.74H12.023zm67.628.001L68.773 90.063h7.045l2.423-8.453h12.964l2.48 8.453h7.1L89.683 51.74H79.65zm-62.668 6.537h.056l4.903 17.076h-9.637l4.678-17.076zm67.628 0h.056l4.903 17.076h-9.637l4.678-17.076z" style="display:inline;fill:current;fill-opacity:1;stroke:none;stroke-width:.408654;stroke-opacity:1" transform="translate(-1.146 -51.177)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
301
static/pageDriver.js
Normal file
301
static/pageDriver.js
Normal file
@ -0,0 +1,301 @@
|
||||
_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]");
|
||||
|
||||
// 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(singleValues);
|
||||
|
||||
// Iterate over all single values
|
||||
singleValues.forEach(async singleValue => {
|
||||
writeSingelton(singleValue);
|
||||
});
|
||||
|
||||
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 => {
|
||||
searchField.addEventListener("input", async function() {
|
||||
console.log("Search field changed: ", searchField);
|
||||
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);
|
||||
const result = await returnTableDataByTableNameWithSearch(table, value);
|
||||
console.log("Result: ", result);
|
||||
clearTable(target);
|
||||
writeDataToTable(target, result);
|
||||
});
|
||||
});
|
||||
|
||||
// Attach listeners to modal forms
|
||||
modalForms.forEach(modalForm => {
|
||||
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 = await createEntry(table, jsonData);
|
||||
console.log("Response: ", resp);
|
||||
if(resp["status"] == "CREATED") {
|
||||
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
|
||||
}
|
||||
|
||||
// const target = document.getElementById(table);
|
||||
// writeDataToTable(target, result);
|
||||
|
||||
|
||||
|
||||
// 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 + "']");
|
||||
if(searchField) {
|
||||
const value = searchField.value;
|
||||
const dbTable = table.getAttribute("data-dataSource");
|
||||
const result = await returnTableDataByTableNameWithSearch(dbTable, value);
|
||||
clearTable(table);
|
||||
writeDataToTable(table, result);
|
||||
} else {
|
||||
const result = await returnTableDataByTableName(table.getAttribute("data-dataSource"));
|
||||
clearTable(table);
|
||||
writeDataToTable(table, result);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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 = [];
|
||||
columns.forEach(column => {
|
||||
requiredCols.push(column.getAttribute("data-dataCol"));
|
||||
});
|
||||
|
||||
for (resultIndex in data) {
|
||||
const row = data[resultIndex];
|
||||
const tr = document.createElement("tr");
|
||||
requiredCols.forEach(column => {
|
||||
const td = document.createElement("td");
|
||||
td.innerHTML = row[column];
|
||||
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-background, .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();
|
||||
}
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user