TheJach.com

Jach's personal blog

(Largely containing a mind-dump to myselves: past, present, and future)
Current favorite quote: "Supposedly smart people are weirdly ignorant of Bayes' Rule." William B Vogt, 2010

Blog rewrite notes - serving the front-end

Last time I wrapped up the actions that the server needs to support. This time I want to write a bit about how I ultimately want to render the front-end...

Starting with how it currently renders, it's done in a rather classic server-side rendering way. I have several "template" files that organize the site content, and each request is handled by applying some template to the dynamic content and sending down the HTML with no further interactions needed.

I'd like to keep everything as noscript friendly as it is. I really dislike the direction modern sites have gone of needing JS for everything, even if there are advantages depending on the tradeoffs you want to make. (Headless CMS is increasingly popular for instance.)

But I'm not sure I want to keep the same classic template style... Or maybe I will, but I will just compose them differently. To explain what I mean we'll need to look at some examples.

In the previous post, if the URL is say /about, then it selects the HomeService's display_about_page. This function is in its entirety:


function display_about_page($params) {
$template_data = array(
'title' => 'About Jach'
, 'content' => 'content/about.tpl'
, 'master' => 'main.tpl'
);
$this->display_page($template_data);
}


It's a pure content page, no dynamicism. It's rather amusing that it's uncached, but in the past I've edited it live and didn't want to have to delete a cache file...

It's pretty clear how this works. There's a display_page function on the base class that takes in what to display, which is the template data map. I've told it the content is the content/about.tpl file, and its "master" is main.tpl.

What are these tpl files? They're really just other PHP files, but mostly contain HTML. If we look at the start of about.tpl, we'll see:


<div class="maincontent">

<h1>Possibly Pondered Probes</h1>

<p>Last updated on April 7, 2020.</p>


This could render by itself just fine, but would lack styles and the rest of the blog shell (header, navigation, etc.) -- that stuff is laid out in the master template file. Let's take a look at main.tpl in almost its entirety (just removed ~18 lines of irrelevancy):


<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><?php echo $title; ?></title>
<link rel="shortcut icon" href="/imgs/favicon.ico" />

<link rel="stylesheet" type="text/css" title="Default Style" href="/css/altstyle.css" />
</head>
<body>

<div class="wrapper">

<div class="head">
<a name="top"></a>
<?php $this->insert_template('content/heading.tpl'); ?>
</div>

<div class="content">

<?php
if(isset($content)) {
if(is_array($content)) {
foreach ($content as $c) {
echo "$c\n\n";
}
} else {
echo $content;
}
}
?>

<br />
</div>

<div class="sidebar">

<?php $vars = (isset($archives)) ? array('archives' => $archives) : array();
$this->insert_template('content/sidebar/archives.tpl', $vars); ?>

<?php
$this->insert_template('content/sidebar/recent_posts.tpl');
?>
<?php $this->insert_template('content/sidebar/recent_comments.tpl'); ?>

<?php $this->insert_template('content/sidebar/tag_cloud.tpl'); ?>

<?php $this->insert_template('content/sidebar/better_sites.tpl'); ?>

<?php
if (isset($_SESSION['group'])) {
if ($_SESSION['group'] == 'root' || $_SESSION['group'] == 'admin') {
$this->insert_template('content/sidebar/admin_cp.tpl');
} elseif($_SESSION['group'] == 'writer') {
$this->insert_template('content/sidebar/writer_cp.tpl');
}
}

if (isset($_SESSION['user_name'])) {
$this->insert_template('content/sidebar/user_cp.tpl');
} else {
$this->insert_template('forms/login.form.tpl');
}
?>

</div>

</div>
<?php $this->insert_template('js.tpl'); ?>
</body>
</html>


As you can see it's pretty straight-forward. insert_template() is just a small wrapper around "require_once $file". It's actually relatively "component-ized" as it stands -- a more modern framework might invent custom XML tags or something instead of calling a function, but the principle is the same. We might make this master "cleaner" by creating a file called sidebar.tpl, whose only job is to include the appropriate sidebar/whatever.tpl files.

The core page content is expected to be in a variable called $content, which we just echo. So it's expected to be HTML. Going back to the display_page() function, the way it works is it first takes the content template file and calls a custom get_include_contents() on it. This function looks like this:


function get_include_contents($file, $vars=array()) {
// precondition: caller checks to see if file exists
extract($vars);
ob_start();
include $file;
$contents = ob_get_contents();
ob_end_clean();
return $contents;
}


Up to this point, nothing should have been echo'd (which in PHP by default sends it straight to the response body). To keep it that way, we create a new output buffer context. This lets us echo freely without the results going straight to the HTTP response. Then the template file is simply included, which will execute it (so the about.tpl can/does contain its own PHP tags), and then finally we capture any of the echo'd text (or pure HTML as PHP's default convenient behavior is to treat text outside of PHP tags as echo'd text) into $contents to return and clean (instead of flush-to-the-response) the output buffer so nothing is sent.

With $contents in hand, we bind that to $content within display_page() and simply require_once the master template main.tpl. Tada!

For completion let's look at a few other interesting bits. When looking at a URL like /view/tags/sometag, display_by_tag() is used. This function takes the tag from the URL, queries the database for a list of post data (title, url, date) that has that tag, and with the two pieces of data concludes with:


function display_by_tag($params) {
$tag = ...;
$posts = ...;
$template_data = array(
'title' => "Posts with tag $tag"
, 'content' => 'content/display_by_tag.tpl'
, 'tag' => $tag
, 'posts' => $posts
, 'master' => 'main.tpl'
);
$this->display_page($template_data);
}


display_by_tag is straightforward:


<?php if(isset($tag) && isset($posts)) { ?>
<div class="maincontent">

<h2>Posts with tag "<?php echo $tag; ?>"</h2>

<?php
foreach ($posts as $post) {
$url = $post['url'];
$title = $post['title'];
$date = $post['date'];
echo "<p><a href=\"$url\">$title - $date</a></p>\n";
}
?>

</div>

<?php } ?>


Overly defensive coding is a sign of a junior. :)

I'll never understand the obsession with template languages. Just use the expected template host language directly like PHP, or at worst JS... Ok I understand a little bit the benefits, like satisfying the desire to prevent less-than-great designers from ruining things by mucking with code when you only want them mucking with HTML or CSS, it's the same desire game developers have when they force people to use a crippled custom language for entity behavior or whatever instead of the underlying real language. I guess all I can say is that some languages are less crippled than others. And there is one other benefit that in the event you do a backend rewrite (say from PHP to Lisp) if I used a standard template system I wouldn't have to touch the templates, assuming I can find an engine in Lisp. But how often does this happen where a backend change isn't going to change how the front-end is served?

Anyway I did promise to show the other interesting bits... the URL /view/tags isn't really meant to be used publicly (thought I think I may have envisioned a JS tag cloud at one point and left it) so if you go to it you'll see some JSON. If you look at the get_tag_list() function, all it does is query for all tags in the DB into an object array and returns it. It doesn't call display_page() or do any echoing itself.

So how does the JSON get there? Well at the higher level that handles the dispatching of the $urls array, it calls the function like so: $result = $this->$call($params);. Then it copies up any content that may have been echoed (such as with display_page()) with ob_get_contents() (the index flow establishes an output buffer early on, eventually render_response() is called which invokes the URL-mapped dispatch function, and the index finishes with an ob_end_flush() to send the response). If the content is empty and the result isn't, rather than returning a blank page, we just assume the result is json-serializable and echo json_encode($result).

A few other functions (mostly in the admin service) are a bit more structured and return a call to ajax_response($data) whose job is to just provide a consistent format for the JSON response of {'msg': msgString, 'error': errorString, other-key-vals}.

The final interesting example of this system is displaying the home page. The home page is really just a list of posts, and clicking previous takes you to /home/page/2 and so on. All the routes are served by the same function. You might think "Why not just have a home page template and give it the page number from the URL, and call display_page()"? I might have done that at some point. But the way it currently works (not saying it's good) is that it instead does:


function display_home_page($params) {
$page = ...;
$posts = $this->get_latest_posts($start, $end);

$wrapper_data = array(
'wrapper_file' => 'content/wrappers/home_page.wrapper.tpl'
, 'page' => $page
, 'posts' => $posts
, 'this' => $this
);
$content = $this->apply_wrapper($wrapper_data);

$template_data = array(
'title' => "The Jach's Website"
, 'master' => 'main.tpl'
, 'content' => $content
);
$this->display_page($template_data);

}


apply_wrapper() is very similar to insert_template(), but it's more "structured". Instead of require_once'ing the file, it requires it, and while nothing prevents it, the expectation is that wrappers don't echo any of their content. Instead, they assign their content to a new $wrapper_data['content'] field, which is then returned via apply_wrapper() for convenience. This makes the template rather ugly as it's really pure PHP, so this is probably why I only have one of these wrappers in use.

Is there a benefit? I use it for displaying posts by date too (e.g. /view/year), and for rendering previews of new posts while writing them... but I probably should have done it just by using the get_include_contents() approach.

Well hopefully that explains the template system I made for my micro-framework. It's really not all that different from something like Django... But I'm left with the question of what I want to do for the proposed Lisp rewrite. I can't just use it as-is because there's too much PHP in there, unless I want to emulate the bits of PHP in use in a parsing step. No thanks.

As the above implies, there are different kinds of templates. My folder structure is templates/ which is meant to contain top-level master templates (currently just main and main_no_sidebar, but also I put js.tpl here for some reason). Inside I have templates/forms/, templates/content/, templates/content/sidebar/, and templates/wrappers/. I think templates/content/ got a bit out of hand -- things like about.tpl are actually "content heavy" and contain (or should contain) very little if any "code", even JavaScript. But as we saw with content/display_by_tag.tpl, while the end result is a piece of content the way it is created is mostly with a PHP loop. Meanwhile we have forms/, some of which (like login.form.tpl) are pure HTML, while others (like comment.form.tpl) have some PHP in there (e.g. to generate the captcha field), and the most complicated one (new_post.form.tpl) contains quite a bit of PHP, HTML, and JavaScript.

With a modern eye, these all really should be one type of thing: "components". But as we see different components have different needs, and the nicest way to author them depends on what those needs are. For pure content components like about.tpl, HTML is a pretty fine authoring format, and can be used by pretty much anyone without much danger. Markdown is nice too but there's always the question of which flavor. For more sophisticated components that actually want to control their layout and styling, and get their content mostly from other components or from code (either JS or back-end functions), you want something like HTML but with programmatic support (like template languages or mixing PHP). And as component sophistication grows, you often want a way to separate the physical files handling the code, the display, the styling, and anything else (such as metadata).

PHP still occupies a nice niche here since it's easy to satisfy all this with your preferred level of structure. You could by convention make these very Vue-like with a <template> tag wrapping your component's display portion and end it with a PHP include for your component's JavaScript, and use PHP itself for any (keeping it simple) template-level programming like loops or ifs you need to have (with troubles coming only when data is only available at runtime).

So this brings me to Lisp. The most popular HTML tie-in with Lisp web servers is probably cl-who, which is a straightforward representation of HTML with s-expressions. I actually favor spinneret more and will probably use that. The example immediately suggests a design for a full site that still is made with the classic "folder of views or templates" idea for server-side rendering. main.tpl would become (say) master-template.lisp, which would expose a single macro with-index taking the expected 'content' and calling other custom "tags" that act like custom components (or calls to insert_template()). The custom tags would be defined in their own files to fully 'compenent-ize' them, and they in turn might define their own other tags for support, or for example an about.lisp file might just define a 'tag' called 'about' that is just one big string. I might need some wrapping support though to make it as convenient as PHP to not worry about escaping (i.e. should type something besides '"' to get out of the big string) and by default evaluate each form as concatenation... but I guess I'll see when I get to it.

The other nice thing about PHP is that I can (ignoring the cache for now) simply edit a .tpl file and upload it (or edit it live on the server) and subsequent page requests will just use the new file. This could be done with Lisp but it requires a bit more work (perhaps someone has already done it). I don't really want to have to restart the server just because I'm deploying changes to a single template, it should just reload the file. I could SSH into the server, connect to the Lisp server's REPL, and edit and hot-load like I would locally, but I'm trying to avoid the pattern of doing it in production... Well, a file watcher really seems like the only sane way to do it, it wouldn't be hard to write one if one isn't already present. If my "components" are just sets of Lisp files, like my "templates" are currently just PHP files, porting everything shouldn't actually be too much hassle. At the same time I can rid myself of ExtJS (now for many years Sencha) dependencies, and for custom JS perhaps write it all with Parenscript.


Posted on 2020-04-09 by Jach

Tags: lisp, php, programming

Permalink: https://www.thejach.com/view/id/372

Trackback URL: https://www.thejach.com/view/2020/4/blog_rewrite_notes_-_serving_the_front-end

Back to the top

Back to the first comment

Comment using the form below

(Only if you want to be notified of further responses, never displayed.)

Your Comment:

LaTeX allowed in comments, use $$\$\$...\$\$$$ to wrap inline and $$[math]...[/math]$$ to wrap blocks.