Skip to content

Binding tables

Greg Bowler edited this page Feb 27, 2023 · 25 revisions

HTML tables are the best way to display tabular data in rows, cells and headings. There are multiple ways of binding tabular data, depending on the use case and the type of data you have.

  • You may choose to bind all rows and columns from your data source directly into an empty HTML table. This will create the appropriate row, header, and cell elements automatically.
  • You may choose to bind a data structure to an HTML table with existing header cells. This will use the existing HTML structure to define the order and presence of each column.
  • You may choose to prescribe exactly where data is bound within your HTML structure, allowing tables to contain hard-coded existing elements, rows, or columns.

There are two types of data structure that we can use to bind table data:

  1. A two dimensional iterable, e.g. iterable<iterable<string>>, where the outer iterable represents the rows, and the inner iterable represents the columns, and the first index of the outer iterable contains the headings.
$data = [
	["Day", "Weather"], // This is the heading row
	["Monday", "Rain"],
	["Tuesday", "Cloud"],
	["Wednesday", "Cloud"],
	["Thursday", "Sun"],
	["Friday", "Sun"],
	["Saturday", "Cloud"],
	["Sunday", "Cloud"],
];
  1. An two dimensional iterable of type iterable<string, iterable<string>>, where the string key of the iterable represents each heading, and the inner iterable represents the cell values for each row.
$data = [
	"Day" => ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
	"Weather" => ["Rain", "Cloud", "Cloud", "Sun", "Sun", "Cloud", "Cloud"],
];

The data in both examples above represents the same data and will bind the same table data. Associative arrays are the simplest data structure in PHP, but any iterable can be used to represent multiple objects, as long as they are Stringable

Binding to an empty table

Binding to a table without any pre-existing HTML Structure is useful for when your data is completely dynamic.

HTML:

<h1>Weather forecast</h1>
<table data-bind:table></table>

PHP:

function example(DocumentBinder $binder):void {
	$data = [
		"Day" => ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
		"Weather" => ["Rain", "Cloud", "Cloud", "Sun", "Sun", "Cloud", "Cloud"],
	];

	$binder->bindTable($data);
}

Output HTML:

<h1>Weather forecast</h1>
<table>
	<thead>
		<tr>
			<th>Day</th>
			<th>Weather</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>Mon</td>
			<td>Rain</td>
		</tr>
		<tr>
			<td>Tue</td>
			<td>Cloud</td>
		</tr>
		<tr>
			<td>Wed</td>
			<td>Cloud</td>
		</tr>
		<tr>
			<td>Thu</td>
			<td>Sun</td>
		</tr>
		<tr>
			<td>Fri</td>
			<td>Sun</td>
		</tr>
		<tr>
			<td>Sat</td>
			<td>Cloud</td>
		</tr>
		<tr>
			<td>Sun</td>
			<td>Cloud</td>
		</tr>
	</tbody>
</table>

Binding to a table with existing headings

In some applications, the available datasets might be more complex than how you would like to output them. There may be many more fields to the dataset than what you want to display.

If the HTML table you bind to already contains a <thead> element, the <th> heading elements will be used to define the order and presence of the fields when binding.

HTML:

<h1>The planets</h1>
<table data-bind:table>
	<thead>
		<tr>
			<th>Name</th>
			<th>Distance from Sun</th>
		</tr>
	</thead>
</table>

PHP:

function example(DocumentBinder $binder):void {
	$data = [
		"Name" => ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"],
		"Symbol" => ["", "", "🜨", "", "", "", "", ""],
		"Diameter" => [4879, 12104, 12756, 6792, 142984, 120536, 51118, 49528],
		"Distance from Sun" => [59.7, 108.2, 149.6, 227.9, 778.6, 1433.5, 2872.5, 4495.1],
	];
	$binder->bindTable($data);
}

Output HTML:

<!doctype html>
<html>

<head></head>

<body>
    <h1>The planets</h1>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Distance from Sun</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Mercury</td>
                <td>59.7</td>
            </tr>
            <tr>
                <td>Venus</td>
                <td>108.2</td>
            </tr>
            <tr>
                <td>Earth</td>
                <td>149.6</td>
            </tr>
            <tr>
                <td>Mars</td>
                <td>227.9</td>
            </tr>
            <tr>
                <td>Jupiter</td>
                <td>778.6</td>
            </tr>
            <tr>
                <td>Saturn</td>
                <td>1433.5</td>
            </tr>
            <tr>
                <td>Uranus</td>
                <td>2872.5</td>
            </tr>
            <tr>
                <td>Neptune</td>
                <td>4495.1</td>
            </tr>
        </tbody>
    </table>
</body>

</html>

When heading text content differs from data keys

You may notice in the above example that the contents of the existing <th> elements perfectly matches the keys of the data. This isn't always possible, especially if your data source is from a database.

You can specify the data key to match by adding a data-table-key attribute to the <th> element. For example, if the data keys for the above example were actually name and dist, you can specify these in the HTML like this:

<th data-table-key="name">Name</th>
<th data-table-key="dist">Distance from Sun</th>

Binding to a table with existing rows and cells

For when complex tables are necessary, it's possible to lay out the table's structure in our source HTML before binding, then using data-bind attributes and data-list attributes to specify exactly how and where data should be bound to the table.

If a table has a data-list attribute within one of the existing children, that child will be used as a list element and cloned for every row of data bound.

In the following example, three columns will be bound to existing cells, and within the existing markup there are other elements that will bind other properties of the data.

Take a look at the first and last column in this example. The headers are labelled "Delete" and "Flag", but contain existing HTML markup to achieve some special functionality. In this example we can see that forms are embedded into the cells that can be used to delete the row, or add a flag message. This functionality is made possible by binding data at particular points throughout the existing markup.

<!doctype html>
<table>
<thead>
	<tr>
		<th>Delete</th>
		<th data-table-key="id">ID</th>
		<th data-table-key="name">Name</th>
		<th data-table-key="code">Code</th>
		<th>Flag</th>
	</tr>
</thead>
<tbody>
	<tr data-list data-bind:class=":deleted">
		<td>
			<form method="post">
				<input type="hidden" name="id" data-bind:value="@name" />
				<button name="do" value="delete">Delete</button>
			</form>
		</td>
		<td></td>
		<td></td>
		<td></td>
		<td>
			<form method="post">
				<input type="hidden" name="id" data-bind:value="@name" />
				<input name="message" />
				<button name="do" value="flag">Flag</button>
			</form>
		</td>
	</tr>
</tbody>
</table>
PHP and output HTML

PHP:

function example(DocumentBinder $binder):void {
	$data = [
		"id" => ["9d3407ac", "0f503f80", "032685b1", "eb08fc32"],
		"code" => ["A44", "B9", "TX420", "TL29"],
		"name" => ["Respiration device N", "Mini filters", "UNG Sponge", "Support Vents"],
		"deleted" => [null, "2022-01-26 17:11:00", null, null],
		"flagged" => [null, null, null, "out of stock"],
	];
	$binder->bindTable($data);
}

Output HTML:

<!doctype html>
<html>
<head></head>
<body>

<table>
<thead>
	<tr>
		<th>Delete</th>
		<th>ID</th>
		<th>Name</th>
		<th>Code</th>
		<th>Flag</th>
	</tr>
</thead>
<tbody id="list-parent-63d2c38f3c39b">
	<tr>
		<td>
			<form method="post">
				<input type="hidden" name="id" value="9d3407ac">
				<button name="do" value="delete">Delete</button>
			</form>
		</td>
		<td>9d3407ac</td>
		<td>Respiration device N</td>
		<td>A44</td>
		<td>
			<form method="post">
				<input type="hidden" name="id" value="9d3407ac">
				<input name="message">
				<button name="do" value="flag">Flag</button>
				<button name="do" value="unflag">Flag</button>
			</form>
		</td>
	</tr>
	<tr>
		<td class="deleted">
			<form method="post">
				<input type="hidden" name="id" value="0f503f80">
				<button name="do" value="delete">Delete</button>
			</form>
		</td>
		<td>0f503f80</td>
		<td>Mini filters</td>
		<td>B9</td>
		<td>
			<form method="post">
				<input type="hidden" name="id" value="0f503f80">
				<input name="message">
				<button name="do" value="flag">Flag</button>
				<button name="do" value="unflag">Flag</button>
			</form>
		</td>
	</tr>
	<tr>
		<td>
			<form method="post">
				<input type="hidden" name="id" value="032685b1">
				<button name="do" value="delete">Delete</button>
			</form>
		</td>
		<td>032685b1</td>
		<td>UNG Sponge</td>
		<td>TX420</td>
		<td>
			<form method="post">
				<input type="hidden" name="id" value="032685b1">
				<input name="message">
				<button name="do" value="flag">Flag</button>
				<button name="do" value="unflag">Flag</button>
			</form>
		</td>
	</tr>
	<tr>
		<td>
			<form method="post">
				<input type="hidden" name="id" value="eb08fc32">
				<button name="do" value="delete">Delete</button>
			</form>
		</td>
		<td>eb08fc32</td>
		<td>Support Vents</td>
		<td>TL29</td>
		<td>
			<form method="post">
				<input type="hidden" name="id" value="eb08fc32">
				<input name="message">
				<button name="do" value="flag">Flag</button>
				<button name="do" value="unflag">Flag</button>
			</form>
		</td>
	</tr>
</tbody>
</table>

</body>
</html>

Next, learn how to use objects to represent bindable data.