Dynamic Editable HTML Table
INTRO
This example will allow you to:
Display an AI2 (in csv format e.g. header row, then data rows) list in an html table.
It will also allow you (with the exception of the header row) to create new rows, edit any row, and delete any row, whilst returning the saved/updated data to your app, you therefore have a full CRUD.
Features:
The html is initially designed to display data you might receive from a database table, starting with an id (primary key), and then the other data columns, for example data returned by a SELECT query from an sqlite/mysql database or similar dataset from a google sheet. Therefore it will not let you edit the id (primary key row) because this is essential unique data and should not be changed. If you add a new row, the table will add the next number.
However, you can use a table that does not have an id column or primary key, and the first column will then be editable.
The html file will also work, to a limited extent, in a computer browser, outside of AI2. This is useful for testing and modifications. You will require the html file, and a suitable data.js file (example also provided in json format) to load some initial data. View the js file to see how your own data should be formatted. I used sessionStorage to save data - this will persist while the browser window is open. You can change this to localStorage for additional persistence if you want.
I used the w3.css framework for most of the layout and formatting
SETUP
You can simply download the demo aia project provided, and open this up in AI2.
Or, copy the provided html file (and image file if you want the "metricrat" icons ;) ), and upload these to your assets. Then (accurately) copy the blocks from the blocks image.
All the files you need are provided below.
HTML
(there is a lot of it, 329 lines....)
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<head>
<title>Dynamic Editable Table</title>
<style>
button {width:30%}
.w3-container {padding-top:10px}
#display {overflow: auto;height: 80vh;}
th[scope="row"], thead tr {position:sticky;top:0;}
th, td {white-space: nowrap;}
</style>
</head>
<body>
<div class="w3-container">
<div id="delmod" class="w3-modal">
<div class="w3-modal-content w3-card-4 w3-round">
<div class="w3-center"><br>
<p>Are you sure you want<br>to delete this record ?</p>
</div>
<div class="w3-bar w3-center">
<button onclick="modResponse('Yes');" type="button" style="width:25%;" class="w3-btn w3-green w3-round w3-small">Yes</button>
<button onclick="modResponse('No');" type="button" style="width:25%;" class="w3-btn w3-red w3-round w3-small">Cancel</button>
</div>
<br>
</div>
</div>
</div>
</div>
<div class = "w3-container w3-bar w3-center">
<button id = "btEdit" class="w3-btn w3-small w3-blue w3-round" onclick="Edit()" disabled>Edit</button>
<button id = "btAdd" class="w3-btn w3-small w3-yellow w3-round" onclick="Add()" disabled>Add</button>
<button id = "btSelect" class="w3-btn w3-small w3-purple w3-round" onclick="Select()" disabled>Select</button>
</div>
<div class = "w3-container w3-bar w3-center">
<button id = "btDelete" class="w3-btn w3-small w3-red w3-round" onclick="Delete()" disabled>Delete</button>
<button id = "btSave" class="w3-btn w3-small w3-green w3-round" onclick="Save()" disabled>Save</button>
<button id = "btCancel" class="w3-btn w3-small w3-orange w3-round" onclick="Cancel()" disabled>Cancel</button>
</div>
<div class = "w3-container">
<div id="display" class = "w3-responsive">Loading table ...</div>
</div>
<br>
<script src = "./tableDemoData.js"></script>
<script>
document.getElementById("btEdit").disabled = false;
document.getElementById("btAdd").disabled = false;
document.getElementById("btSelect").disabled = false;
var tableData;
var colLength = "";
var rowSelect = "off";
var rec;
var SelectedRow = "";
if (window.AppInventor) {
var arr = JSON.parse(window.AppInventor.getWebViewString());
tableData = convertArrayToJson(arr);
} else {
if (sessionStorage.Table) {
tableData = JSON.parse(sessionStorage.Table);
} else {
tableData = tableDemoData;
}
}
const createHead = (headObject) => {
let cells = "";
for (const key in headObject) {
cells += `<th id = ${key}>${key}</th>`;
}
return `<tr class = 'w3-green'>${cells}</tr>`;
};
const createRow = (rowObject) => {
let cells = "";
for (const key in rowObject) {
cells += `<td>${rowObject[key]}</td>`;
}
return `<tr onclick="highlight(this);">${cells}</tr>`;
};
const createTable = (tableData) => {
const rows = tableData.map(d => {
return createRow(d)
});
const headRow = createHead(tableData[0])
colLength = (headRow.match(/<th id/g) || []).length;
let optimizedRows = '';
rows.forEach((row) => {
optimizedRows += row;
});
const table = (`
<table id = "det" class = "w3-table-all w3-card-4">
<thead>${headRow}</thead>
<tbody>${optimizedRows}</tbody>
</div>
`);
document.getElementById("display").innerHTML = table;
};
createTable(tableData);
function Edit() {
document.getElementById("btCancel").disabled = false;
document.getElementById("btSave").disabled = false;
document.getElementById("btSelect").disabled = true;
document.getElementById("btAdd").disabled = true;
document.getElementById("btEdit").disabled = true;
const element = document.getElementById("display");
const nodes = element.getElementsByTagName("td");
if (testArrayUID('det','id')) {
for (let i = 0; i < nodes.length; i++) {
if (i != 0 && i%colLength != 0) {
nodes[i].contentEditable = "true";
}
}
} else {
for (let i = 0; i < nodes.length; i++) {
nodes[i].contentEditable = "true";
}
}
};
function Add() {
document.getElementById("btCancel").disabled = false;
document.getElementById("btSave").disabled = false;
document.getElementById("btEdit").disabled = true;
document.getElementById("btSelect").disabled = true;
var maxId = getIdArray("det","id");
var det = tableToArray('det');
var addArr = [];
if (maxId == null) {
addArr.push(maxId);
} else {
addArr.push(maxId+1);
}
for (i=1;i<colLength;i++) {
addArr.push(null);
}
det.push(addArr);
document.getElementById("det").remove();
createTable(convertArrayToJson(det));
};
function Select() {
if (rowSelect == "off") {
rowSelect = "on";
document.getElementById("btSelect").innerHTML = "DeSelect";
document.getElementById("btAdd").disabled = true;
document.getElementById("btEdit").disabled = true;
} else {
rowSelect = "off"
document.getElementById("btSelect").innerHTML = "Select";
document.getElementById("btAdd").disabled = false;
document.getElementById("btEdit").disabled = false;
document.getElementById("btDelete").disabled = true;
deHighlight();
}
};
function Delete() {
rec = getSelectedRowValue();
document.getElementById('delmod').style.display='block';
};
function Save() {
document.getElementById("btCancel").disabled = true;
document.getElementById("btSave").disabled = true;
document.getElementById("btSelect").disabled = false;
document.getElementById("btAdd").disabled = false;
document.getElementById("btEdit").disabled = false;
const element = document.getElementById("display");
const nodes = element.getElementsByTagName("td");
for (let i = 0; i < nodes.length; i++) {
nodes[i].contentEditable = "false";
}
if (window.AppInventor) {
var det = tableToArray('det');
window.AppInventor.setWebViewString(JSON.stringify(det));
document.getElementById("det").remove();
createTable(convertArrayToJson(det));
} else {
var det = tableToArray('det');
var jsonObject = convertArrayToJson(det);
sessionStorage.Table = JSON.stringify(jsonObject);
document.getElementById("det").remove();
createTable(jsonObject);
}
};
function Cancel() {
document.getElementById("btCancel").disabled = true;
document.getElementById("btSave").disabled = true;
document.getElementById("btSelect").disabled = false;
document.getElementById("btAdd").disabled = false;
document.getElementById("btEdit").disabled = false;
document.getElementById("det").remove();
createTable(tableData);
};
function tableToArray(tableId) {
myData = document.getElementById(tableId).rows
myList = []
for (var i = 0; i < myData.length; i++) {
el = myData[i].children
myEl = []
for (var j = 0; j < el.length; j++) {
myEl.push(el[j].innerText);
}
myList.push(myEl)
}
return myList
};
function getIdArray(tableId,colId) {
var myTab = document.getElementById(tableId);
var index = document.getElementById(colId).cellIndex;
var maxId = [];
for (i = 1; i < myTab.rows.length; i++) {
var objCells = myTab.rows.item(i).cells;
for (var j = index; j <= index; j++) {
maxId.push(objCells.item(j).innerHTML);
}
}
if( Math.max(...maxId) == NaN) {
return null
} else {
return Math.max(...maxId)
}
};
function convertArrayToJson(theArray) {
const [keys, ...values] = theArray;
const JsonObject = values.map(array => array.reduce((a, v, i) => ({...a, [keys[i]]: v}), {}));
return JsonObject;
}
function highlight(row) {
if (rowSelect == "on") {
document.getElementById("btDelete").disabled = false;
SelectedRow = row.cells[0].textContent;
deHighlight();
row.style.backgroundColor = 'palegreen';
row.classList.toggle("selectedRow");
}
}
function deHighlight() {
let table = document.getElementById("det");
let rows = table.rows;
for (let i = 0; i < rows.length; i++) {
if ( i%2 == 0 ) {
rows[i].style.backgroundColor = "#f1f1f1";
} else {
rows[i].style.backgroundColor = "transparent";
}
}
}
function getSelectedRowValue() {
deHighlight();
return SelectedRow;
}
function modResponse(resp) {
document.getElementById('delmod').style.display='none';
if (resp == 'Yes') {
var det = tableToArray('det');
for (var i = 0; i < det.length; i++) {
if (det[i][0] == rec) {
det.splice(i,1);
createTable(convertArrayToJson(det));
}
}
if (window.AppInventor) {
window.AppInventor.setWebViewString(JSON.stringify(det));
} else {
sessionStorage.Table = JSON.stringify(convertArrayToJson(det));
}
}
Select();
};
function testArrayUID(tableId,colId) {
var myTab = document.getElementById(tableId);
var index = document.getElementById(colId).cellIndex;
var colVals = [];
for (i = 1; i < myTab.rows.length; i++) {
var objCells = myTab.rows.item(i).cells;
for (var j = index; j <= index; j++) {
colVals.push(objCells.item(j).innerHTML);
}
}
var test;
for (var i=0;i<colVals.length;i++) {
if (Number.isInteger(parseInt(colVals[i]))){
test = true;
} else {
test = false;
break
}
}
if(tableData.length == new Set(colVals).size && test == true) {
return true;
} else {
return false;
}
};
</script>
</body>
</html>
BLOCKS
VIDEO
to follow....
RESOURCES
Stack Overflow - thanks to all the great people for their solutions to html, javascript and css ideas, answers and solutions
W3Schools - for the w3.css framework, and for the vast html, javascript and css resource
Other internet sources too numerous to mention individually
Link to topic on the AI2 community where you can ask questions, make comment....