App: htmx - Page: examples/cart
PHP
examples/cart.php
|
HTML
examples/cart.pad
|
Result
|
<?php
// The cart is the session's: product id => quantity. A button posts its product - op=add // or op=remove - and the answer is the {fragment 'item'} of that product: the card with its // new count, then the parts elsewhere on the page that changed too, each marked // hx-swap-oob - htmx puts the card back where the button was and the rest where their ids // are: the count in the header, the summary beside the shelf. Emptying the cart is an // ordinary {form} post that ends in a redirect, the page coming back whole.
$counts = padSession ( 'htmxCart', [] ); $products = array_slice ( htmxData ( 'products' ), 0, 8 ); $posted = padRequestIs ( 'POST' );
if ( $posted ) {
$id = (int) padRequest ( 'id', 0 ); $op = htmxText ( 'op' ); $stock = array_column ( $products, 'stock', 'id' );
if ( $op == 'clear' ) { padSessionPut ( 'htmxCart', [] ); padRedirect (); }
if ( $op == 'add' and ( $stock [$id] ?? 0 ) > ( $counts [$id] ?? 0 ) ) $counts [$id] = ( $counts [$id] ?? 0 ) + 1; elseif ( $op == 'remove' and isset ( $counts [$id] ) and --$counts [$id] <= 0 ) unset ( $counts [$id] );
padSessionPut ( 'htmxCart', $counts );
$products = array_values ( array_filter ( $products, fn ( $product ) => $product ['id'] == $id ) );
// A product the shelf does not have - a stale or made-up request - has no card to // answer: a 404, which htmx swaps nothing for, where the empty loop rendered no // {fragment 'item'} and the answer was a 500, "there is no fragment named 'item'".
if ( ! $products ) padAbort ( 404, 'There is no such product' );
}
foreach ( $products as $at => $product ) $products [$at] ['inCart'] = $counts [ $product ['id'] ] ?? 0;
$cart = htmxCart ( $counts ); $cartCount = $cart ['count']; $lines = $cart ['lines']; $total = $cart ['total'];
?>
|
<div class="shop"> <div id="shelf" class="grid shelf"> {products} {fragment 'item'} <div class="card item" id="item-{$id}"> <div class="item-top"><span class="item-emoji">{$emoji}</span>{if $inCart}<span class="badge is-accent">{$inCart} in cart</span>{/if}</div> <strong>{$name}</strong> <div class="muted small">{$price | currency('EUR')} · {if $stock}{$stock} in stock{else}sold out{/if}</div> <div class="item-buttons"> <button class="btn-sm" hx-post="?examples/cart&id={$id}&op=add&padFragment=item" hx-target="#item-{$id}" hx-swap="outerHTML">+ Add</button> {if $inCart}<button class="btn-sm btn-ghost" hx-post="?examples/cart&id={$id}&op=remove&padFragment=item" hx-target="#item-{$id}" hx-swap="outerHTML">โ</button>{/if} </div> </div> {if $posted} <span id="cart-count" hx-swap-oob="true">{$cartCount}</span> {cartSummary} {/if} {/fragment} {/products} </div> {cartSummary} </div>
|
|
App: htmx - Page: examples/scroll
PHP
examples/scroll.php
|
HTML
examples/scroll.pad
|
Result
|
<?php
// Ninety orders, fifteen at a time. The page shows the first fifteen; the last row of every // batch carries hx-get for the next batch, sent when the row scrolls into view. That row // has no id, so the request names its fragment itself: &padFragment=rows - the answer is // the next rows, the last of them asking for the batch after it, and hx-swap="afterend" // puts them below the row that asked.
$page = max ( 1, (int) padRequest ( 'p', 1 ) ); $per = 15; $total = 90; $pages = (int) ceil ( $total / $per ); $page = min ( $page, $pages );
$people = [ 'Ada', 'Bram', 'Chen', 'Dalia', 'Emeka', 'Freya', 'Goran', 'Hana' ]; $states = [ 'paid', 'shipped', 'open', 'paid', 'returned' ];
$orders = [];
for ( $n = ( $page - 1 ) * $per + 1; $n <= min ( $total, $page * $per ); $n++ ) $orders [] = [ 'number' => 1000 + $n, 'customer' => $people [ ( $n * 3 ) % count ( $people ) ], 'amount' => 20 + ( $n * 37 ) % 480, 'status' => $states [ $n % count ( $states ) ], 'next' => ( $n == $page * $per and $page < $pages ) ? $page + 1 : 0 ];
?>
|
<table class="table"> <thead><tr><th>Order</th><th>Customer</th><th>Status</th><th class="num">Amount</th></tr></thead> <tbody> {fragment 'rows'} {orders} <tr{if $next} hx-get="?examples/scroll&p={$next}&padFragment=rows" hx-trigger="revealed" hx-swap="afterend"{/if}> <td>#{$number}</td> <td>{$customer}</td> <td><span class="badge status-{$status}">{$status}</span></td> <td class="num">{$amount | currency('EUR')}</td> </tr> {/orders} {/fragment} </tbody> </table> <p class="muted small">Scroll down - every fifteenth row asks for the next fifteen. Ninety in all.</p>
|
|
App: htmx - Page: examples/search
PHP
examples/search.php
|
HTML
examples/search.pad
|
Result
|
<?php
// The products whose name, category or text holds the query. The same PHP serves the page // and every answer to htmx: the input's hx-target="#results" sends HX-Target: results, // and PAD answers the {fragment 'results'} of search.pad alone. The query stands in the // address (hx-push-url), so a reload - or a link - shows the same results whole.
$q = trim ( htmxText ( 'q' ) );
$found = array_values ( array_filter ( htmxData ( 'products' ), fn ( $product ) => $q === '' or stripos ( $product ['name'] . ' ' . $product ['category'] . ' ' . $product ['blurb'], $q ) !== FALSE ) );
$count = count ( $found );
?>
|
<div class="toolbar"> <div class="search"> <input class="input" type="search" name="q" value="{$q}" placeholder="Search - try lamp, audio or usb" autocomplete="off" aria-label="Search the products" hx-get="?examples/search" hx-trigger="input changed delay:250ms, search" hx-target="#results" hx-push-url="true" hx-indicator="#searching"> </div> <span id="searching" class="htmx-indicator spinner" aria-hidden="true"></span> </div>
<div id="results"> {fragment 'results'} <p class="muted small">{$count} {if $count eq 1}product{else}products{/if}{if $q} for <mark>{$q}</mark>{/if}</p> <table class="table"> <thead><tr><th>Product</th><th>Category</th><th class="num">Price</th></tr></thead> <tbody> {found} <tr><td>{$emoji} {$name}</td><td><span class="badge">{$category}</span></td><td class="num">{$price | currency('EUR')}</td></tr> {/found} </tbody> </table> {if $count eq 0}<div class="empty-state"><span class="big">๐</span>Nothing in the catalogue matches.</div>{/if} {/fragment} </div>
|
|
App: manual - Page: fragments/locale_1
PHP
fragments/locale_1.php
|
HTML
fragments/locale_1.pad
|
Result
|
<?php
// The page picks Dutch; an application listing $padLocales lets the visitor's // Accept-Language or ?lang= choose instead.
$padLocale = 'nl';
$count = 3; $user = 'Ann'; $total = 1234.5; $when = '2026-10-05';
?>
|
<h4>{trans 'cart.title'}</h4> <p>{trans 'cart.items', count=$count} - {trans 'cart.owner', name=$user}</p> <p>{$total | currency('EUR', 'nl_NL')} - {$when | localDate('d MMMM y')}</p>
|
|
App: regression/errors - Page: syntax/a_currency_locale_intl_does_not_know
PHP
|
HTML
syntax/a_currency_locale_intl_does_not_know.pad
|
Result
|
|
{echo 5 | currency('EUR', 'xx_YY')}
|
|
App: regression/framework - Page: functions/currency_of_an_empty_value_is_empty
PHP
|
HTML
functions/currency_of_an_empty_value_is_empty.pad
|
Result
|
|
{data 'rows'}[{"price":null},{"price":12.5}]{/data}{rows}[{$price | currency('EUR', 'en')}]{/rows}[{echo '' | currency}]
|
|
App: regression/framework - Page: functions/currency_writes_an_amount_the_locale_s_way
PHP
|
HTML
functions/currency_writes_an_amount_the_locale_s_way.pad
|
Result
|
|
[{echo 1234.5 | currency('USD', 'en_US')}][{echo 99 | currency('EUR', 'en')}]
|
|
App: regression/framework - Page: lenient/a_locale_intl_does_not_know_falls_back_to_the_plain_form
PHP
lenient/a_locale_intl_does_not_know_falls_back_to_the_plain_form.php
|
HTML
lenient/a_locale_intl_does_not_know_falls_back_to_the_plain_form.pad
|
Result
|
<?php
// The lenient walk under test: with the syntax check off, what cannot run passes the // value through or yields empty, and the walk carries on.
$padCheckSyntax = FALSE;
?>
|
[{echo 1234.5 | currency('EUR', 'xx_YY')}][{echo '2026-10-05' | localDate('long', 'none', 'zz')}]
|
|
App: regression/framework - Page: tags/datatable_sums_the_totals_columns_and_aligns_the_numbers
PHP
|
HTML
tags/datatable_sums_the_totals_columns_and_aligns_the_numbers.pad
|
Result
|
|
{datatable totals='qty, amount', labels='Item, Qty, Amount, Code', format="amount:currency('EUR')", caption='Lines <b>', striped} item,qty,amount,zip_code Chair <b>,2,10.5,1234 Table,1,120,98765 Lamp,10,3.25,5000 {/datatable}
|
|
App: showcase - Page: datatable/expenses
PHP
|
HTML
datatable/expenses.pad
|
Result
|
|
{meta title='A table from inline CSV', about='As a pair the content is the data - CSV, JSON, YAML or XML - here a budget with a pipe formatting the amounts and a totals row.'} {datatable totals='budget, spent', format="budget:currency('EUR'), spent:currency('EUR')", caption='Marketing budget, third quarter'} channel,budget,spent Search ads,12000,11380.40 Social media,8500,9120.15 Newsletter,1500,960 Trade fair,15000,14215.80 Print,3000,2410.25 {/datatable}
|
|
App: showcase - Page: pdf/invoice
PHP
pdf/invoice.php
|
HTML
pdf/invoice.pad
|
Result
|
<?php
$invoice = '2026-114'; $issued = '8 October 2026'; $due = '7 November 2026'; $customer = [ 'name' => 'Noordkaap Sailing School', 'street' => 'Havenkade 12', 'city' => '1781 AB Den Helder' ];
$lines = [ [ 'item' => 'Mooring rope, 12 mm, per 20 m', 'qty' => 4, 'price' => 38.50 ], [ 'item' => 'Fender, inflatable, large', 'qty' => 6, 'price' => 27.95 ], [ 'item' => 'Life jacket 150 N, adult', 'qty' => 10, 'price' => 64.00 ], [ 'item' => 'Shackle, stainless, 8 mm', 'qty' => 24, 'price' => 3.20 ], [ 'item' => 'Delivery to the harbour', 'qty' => 1, 'price' => 45.00 ] ];
$subtotal = 0;
foreach ( $lines as $key => $line ) { $lines [$key] ['amount'] = $line ['qty'] * $line ['price']; $subtotal += $lines [$key] ['amount']; }
$vat = round ( $subtotal * 0.21, 2 ); $total = $subtotal + $vat;
?>
|
{meta title='An invoice as PDF', about='An order rendered on a sheet, the same template one click away as an A4 PDF made in plain PHP.'} {pdf preview, file='invoice-{$invoice}', title='Invoice {$invoice}'} <img src="photos/logo.jpg" width="56" height="56" alt="Harbour Supplies"> <h1>Invoice {$invoice}</h1> <p><b>Harbour Supplies</b><br>Kaai 3, 1781 AA Den Helder<br>info@harbour.example</p> <p>To: <b>{$customer.name}</b><br>{$customer.street}<br>{$customer.city}</p> <p>Issued {$issued} - due {$due}</p> <table> <tr><th>Item</th><th align="right">Qty</th><th align="right">Price</th><th align="right">Amount</th></tr> {lines} <tr><td>{$item}</td><td align="right">{$qty}</td><td align="right">{$price | currency('EUR', 'nl_NL')}</td><td align="right">{$amount | currency('EUR', 'nl_NL')}</td></tr> {/lines} <tr><td colspan="3" align="right">Subtotal</td><td align="right">{$subtotal | currency('EUR', 'nl_NL')}</td></tr> <tr><td colspan="3" align="right">VAT 21%</td><td align="right">{$vat | currency('EUR', 'nl_NL')}</td></tr> <tr><td colspan="3" align="right"><b>Total</b></td><td align="right"><b>{$total | currency('EUR', 'nl_NL')}</b></td></tr> </table> <p>Please pay within 30 days to <b>NL91 ABNA 0417 1643 00</b>, quoting invoice {$invoice}.</p> {/pdf}
|
|
App: tags - Page: examples/datatable/2-csv
PHP
|
HTML
examples/datatable/2-csv.pad
|
Result
|
|
{meta title='Rows as content', about='As a pair the content is the data - here CSV - with a currency pipe and a totals row.'} {datatable totals='budget, spent', format="budget:currency('EUR'), spent:currency('EUR')", caption='Marketing budget'} channel,budget,spent Search ads,12000,11380.40 Social media,8500,9120.15 Newsletter,1500,960 {/datatable}
|
|
|