sercrod

JS spreadsheet (demo)

This example shows how far you can go with Sercrod, plain HTML, and a tiny amount of JavaScript:

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

%editing_addr%
%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: