JS spreadsheet (demo)
This example shows how far you can go with Sercrod, plain HTML, and a tiny amount of JavaScript:
- add rows and columns dynamically,
- type simple formulas into cells,
- recalculate a sheet, and save / load its state.
It is intentionally small and self-contained, so you can copy the code and adapt it to your own use cases.
Code
Here is a standalone HTML file version of the demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sercrod JS Spreadsheet</title>
<script src="/sercrod.js"></script>
<script>
// Column label helper: 1 → A, 2 → B, ..., 27 → AA, ...
function nab_col_label(num){
var label = "";
while(num > 0){
var r = (num - 1) % 26;
label = String.fromCharCode(65 + r) + label;
num = Math.floor((num - 1) / 26);
}
return label;
}
// Evaluate one cell and store the result into values[key]
function nab_eval_cell(values, key, src){
if(src === ""){
delete values[key];
return;
}
try{
values[key] = (function(vals, code){
with(vals){ return eval(code); }
})(values, src);
}catch(e){
values[key] = "#ERR";
}
}
// Recalculate all expressions in exprs, in key order
function nab_recalc_all(exprs, values){
Object.keys(values).forEach(function(k){
delete values[k];
});
Object.keys(exprs).forEach(function(k){
var src = exprs[k];
if(typeof src === "string"){
nab_eval_cell(values, k, src);
}
});
}
// Initial data for the sheet
var sheet_data = {
rows: [],
cols: [],
exprs: {},
values: {},
editing_addr: "",
editing_expr: ""
};
</script>
<link rel="stylesheet" href="/assets/jspreadsheet.css">
</head>
<body>
<serc-rod id="sheet" data="sheet_data">
<h2>Sercrod JS Spreadsheet</h2>
<!-- Add rows and columns -->
<div>
<button @click="rows.push(rows.length + 1)">Add row</button>
<button @click="
var n = cols.length;
var label = nab_col_label(n + 1);
cols.push(label);
">Add column</button>
</div>
<!-- Formula bar -->
<div>
<span>%editing_addr%</span>
<input type="text"
placeholder="Type a JS expression, for example: a1 + b2 * 3"
*input="editing_expr"
@keyup="
if(!editing_addr) return;
var v = event.target.value;
editing_expr = v;
var key = editing_addr.toLowerCase(); // 'A1' → 'a1'
exprs[key] = v;
nab_recalc_all(exprs, values);
"
>
</div>
<!-- Main table -->
<table border="1" cellpadding="4">
<thead>
<tr>
<th></th>
<th *for="c of cols">%c%</th>
</tr>
</thead>
<tbody>
<tr *for="r of rows">
<th>%r%</th>
<td *for="c of cols"
@click="
editing_addr = c + String(r); // visible address, for example 'A1'
var key = editing_addr.toLowerCase(); // internal key, for example 'a1'
editing_expr = exprs[key] || '';
"
>
<!-- Edit mode: show textarea for the current cell -->
<div *if="editing_addr === c + String(r)">
<textarea @keyup="
if(!editing_addr) return;
var v2 = event.target.value;
editing_expr = v2;
var key2 = editing_addr.toLowerCase();
exprs[key2] = v2;
nab_recalc_all(exprs, values);
">%editing_expr%</textarea>
</div>
<!-- Display mode -->
<div *else>
<span *print="
(function(){
var key3 = (c + String(r)).toLowerCase();
return Object.prototype.hasOwnProperty.call(values, key3)
? values[key3]
: '';
})()
"></span>
</div>
</td>
</tr>
</tbody>
</table>
<div>
<button *save>Save</button>
<button *load>Load</button>
</div>
</serc-rod>
</body>
</html>
This is a demo-level recalculation engine. It simply walks through
all keys in exprs once. It does not build a dependency
graph or detect cycles. For serious use, you would want to add your
own dependency tracking and error handling.
Sample
Below is the same spreadsheet running inside this site. Try adding
rows and columns, and type simple formulas such as
a1 + b1 into cells.
JS spreadsheet
| %c% | |
|---|---|
| %r% |
|
This example uses *save and *load to store
the sheet data in the browser (for example, in localStorage),
so your edits can survive a page reload during a single session.
Where next
JS spreadsheet is an advanced demo. For more examples and patterns, see: