Declare and update variables inside a template with #{ … }#, and carry a running total across a loop.
A statement block runs code without writing anything to the output. That is what lets a template accumulate a subtotal while it renders the rows, then use it in the summary lines underneath.
<style>
body { font: 14px/1.5 system-ui, sans-serif; margin: 0; color: #24292f; }
table { border-collapse: collapse; width: 100%; max-width: 30em; }
th, td { padding: 5px 8px; text-align: left; }
th { font-weight: 600; border-bottom: 2px solid #d8dee4; }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
tr.sum td { border-top: 1px solid #d8dee4; }
tr.total td { border-top: 2px solid #24292f; font-weight: 700; }
.label { color: #57606a; }
</style>
<script type="text/template" id="invoice">
#{ let subtotal = 0; }#
<table>
<thead>
<tr><th>No.</th><th>Item</th><th class="num">Qty</th><th class="num">Unit</th><th class="num">Line</th></tr>
</thead>
<tbody>
#for(let i = 0; i < lines.length; i++) {#
#{ let line = lines[i]; let amount = line.qty * line.unit; subtotal += amount; }#
<tr>
<td class="label">#i + 1#</td>
<td>#line.name#</td>
<td class="num">#line.qty#</td>
<td class="num">#line.unit#</td>
<td class="num">#amount#</td>
</tr>
#}#
#{ let vat = Math.round(subtotal * vatRate); }#
<tr class="sum"><td colspan="4" class="label">Subtotal</td><td class="num">#subtotal#</td></tr>
<tr><td colspan="4" class="label">VAT #vatRate * 100#%</td><td class="num">#vat#</td></tr>
<tr class="total"><td colspan="4">Total</td><td class="num">#subtotal + vat#</td></tr>
</tbody>
</table>
</script>
<div id="output"></div>
<script src="https://cdn.jsdelivr.net/gh/richdafunk/hashJS@v1.3.6/hashJS.js"></script>
<script>
const data = {
vatRate: 0.25,
lines: [
{ name: 'Design retainer', qty: 1, unit: 18000 },
{ name: 'Development hours', qty: 42, unit: 1150 },
{ name: 'Hosting, 12 months', qty: 12, unit: 490 }
]
};
new hashJS('invoice', data, 'output');
</script> The distinction is between the two forms. #…# evaluates an expression and appends the result; #{ … }# runs statements and appends nothing. Everything else follows from normal JavaScript scoping: subtotal is declared before the loop and survives it, while amount is declared inside the loop body and is fresh on every row.
This example also uses a counting for loop rather than for…of, so the row number is available as #i + 1#. Both compile to the loop you wrote; neither is a special case.
Because the total is computed during rendering rather than prepared beforehand, the template stays correct when the caller adds a line — there is no second place to update.