Data files with a tag
A custom tag of the application reads _data/products.json and writes it into a data attribute; a product grid filters and sorts it in the browser. Bare React, no runtime.
_tags/json.php_data/
datasetcreateRoot
Live
How it is built
PAD, on the server React, in the browser
<div id="products" data-products="{json 'products' | ignore}"></div>
<script type="text/babel" src="{$padRoot}react/examples/products.js"></script>// Bare React, no runtime: the component finds its element, reads the JSON the {json} tag
// wrote into data-products (_tags/json.php reads _data/products.json), and renders a
// product grid that filters and sorts in the browser.
function ProductGrid() {
const products = React.useMemo(() => JSON.parse(document.getElementById('products').dataset.products), []);
const categories = ['All', ...new Set(products.map(product => product.category))];
const [category, setCategory] = React.useState('All');
const [sort, setSort] = React.useState('name');
const [inStock, setInStock] = React.useState(false);
const shown = products
.filter(product => category === 'All' || product.category === category)
.filter(product => !inStock || product.stock > 0)
.sort({
name: (a, b) => a.name.localeCompare(b.name),
cheap: (a, b) => a.price - b.price,
dear: (a, b) => b.price - a.price,
rating: (a, b) => b.rating - a.rating
}[sort]);
return (
<div>
<div className="toolbar">
<div className="pills" role="group" aria-label="Category">
{categories.map(name => (
<button key={name} className={'pill' + (name === category ? ' is-on' : '')} onClick={() => setCategory(name)}>{name}</button>
))}
</div>
<label className="small" style={{ display: 'flex', gap: 6, alignItems: 'center', marginLeft: 'auto' }}>
<input type="checkbox" checked={inStock} onChange={event => setInStock(event.target.checked)} /> in stock
</label>
<select className="select" value={sort} onChange={event => setSort(event.target.value)} aria-label="Sort">
<option value="name">Name</option>
<option value="cheap">Price, low first</option>
<option value="dear">Price, high first</option>
<option value="rating">Rating</option>
</select>
</div>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(210px, 1fr))' }}>
{shown.map(product => <ProductTile key={product.id} product={product} />)}
</div>
<p className="muted small" style={{ marginTop: 14 }}>{shown.length} of {products.length} products</p>
</div>
);
}
function ProductTile({ product }) {
return (
<div className="card rise" style={{ padding: 18 }}>
<div style={{ fontSize: 40, lineHeight: 1 }}>{product.emoji}</div>
<h3 style={{ fontSize: 16, margin: '12px 0 2px' }}>{product.name}</h3>
<div className="muted small" style={{ minHeight: 44 }}>{product.blurb}</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 10 }}>
<strong>{money(product.price)}</strong>
<Stars rating={product.rating} />
</div>
<div style={{ marginTop: 8 }}>
{product.stock > 0
? <span className="badge is-good">{product.stock} in stock</span>
: <span className="badge is-bad">sold out</span>}
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('products')).render(<ProductGrid />);<?php
// Read JSON file from _data/
$jsonContent = file_get_contents(APP . "_data/$padParm.json");
// Compact JSON by removing whitespace (decode then re-encode without pretty print)
$jsonData = json_decode($jsonContent, true);
$jsonCompact = json_encode($jsonData);
// HTML-escape for use in attributes
$padContent = htmlspecialchars($jsonCompact, ENT_QUOTES, 'UTF-8');
return TRUE;
?>