Navigating our data is now a simple case of calling paginate on the query and rendering the resulting data table along with the prev/next links.
When the user clicks Prev or Next, the browser makes a full request for the HTML page which replaces everything.
If we use htmx we can take advantage of its ability to request partial results from the server and slot them into the existing web page.
This makes the navigation experience much slicker and more lightweight.
We pass down the full page on first load and then only return the HTML <table> portion for any subsequent data pages.
To do this, we'll modify the previous pagination code as follows:
- load htmx via a
<script>tag - wrap the HTML rendered table in a
<div>with a given class and add an htmxhx-boostattribute, with atargetof that class, so that the links within will kick off htmx partial loads when clicked - register another template that just has the HTML for the table
- change the
index_handlerto return the new template if the request is from htmx - add an http response header,
Content-Security-Policy, to permit the browser to load the htmx javascript library from an external site
We'll first pull the common HTML for the table into a re-useable variable, supplier_table.
And we'll move the template_register code alongside it - it doesn't need to be in the main function, it just needs to run before template_execute.
In a more complex system, this, and the template contents themselves, would be stored in separate files and loaded in for template registration.
supplier_table := «
<div class="paged" hx-boost:inherited="target:'closest .paged'">
« render .data »
<a href="?p=« .prev_page »">Prev</a>
<a href="?n=« .next_page »">Next</a>
</div>
»
template_register("b1",
{template_name:str, template_content:str}{
["supplier", «<!doctype html><title>Ra</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"></script>
<h1>Suppliers</h1>»" + supplier_table],
["supplier_table", supplier_table]
})
Two templates are now registered: supplier, for the whole page which includes the <script> entry to load the htmx library; and supplier_table, for just the table.
Notice that we've wrapped the table render and the links in supplier_table with a <div class="paged"> wrapper.
Also the prev/next links now inherit an htmx attribute,
hx-boost:inherited="target:'closest .paged'",
which tells htmx to handle the clicks and use the result as a replacement for the closest element with a class of "paged", i.e. the new <div class="paged">.
Now here's the rest of the code:
import("http")
/* the template_register code above could go here, or in init.ra */
route := {path:str, relgen:str}{
["/", "index_handler"]
}
index_handler := {attr_types(http.hh)} begin
template_name := "supplier" // return full html page
if not empty(header[k="Hx-Request"]) begin
template_name := "supplier_table" // return only the <table>
end
mydata := paginate($S, {SNO}, 3, {SNO, SNAME, STATUS, CITY}, form[k="p"], form[k="n"])
response := http.response{*,
header:=dee(seq:=0, k:="Content-Security-Policy", v:="script-src 'unsafe-inline' https://cdn.jsdelivr.net"),
body:=template_execute("b1", template_name, mydata)
}
yield
end
main := {} begin
res := http.serve(route:=route)
print(res)
end
The index_handler now chooses between the two templates based on whether Hx-Request (sent by htmx requests) is in the http request header or not.
We also now return the "Content-Security-Policy" response header to allow the external htmx script to run. Like form, header has seq, k and v attributes ({seq:int, k:str, v:str}).
In a browser, hitting http://localhost:8080 would now give:
Suppliers
This looks the same as before, and again the links are unstyled and for display only here, but if javascript is enabled then clicking the prev/next links will request only the HTML table, which will replace the previous version in-place without a page reload. If javascript is not enabled (or not available), then the links gracefully fall back to standard links, i.e. the whole page is reloaded each time.
See the htmx documentation for more ideas on how to enhance the standard web experience.
We now have a single process data journey of: data loaded from table(s) on disk, passing through Ra query operators, and then sent directly as HTML table(s) to the browser.
No sub-languages, no ORMs, no APIs, no JSON, no frameworks: simply relational algebra with results rendered as tables.