WP REST API Query Demo Theme 03

Laying down the base to support more template engines.

Catch-up

I’ve decided to develop a WordPress theme that would allow me to have a technical example of how the WordPress REST API can be used to render not just AJAX response results but the initial state as well using PHP for it.
The “REST Query Demo” theme is on GitHub for my future reference.
The solution is but currently implemented using Handlebars, my template engine of choice, in its handlebars.php and handlebars.js versions.
I’m curious to find if and how the same result could be obtained with other template engines and while I’ve given a theoretical positive answer there is little, in development, that talks as code does.
After a quick research I’ve decided to add the code needed to use other template engines.
Required condition for this to happen is that those template engines should have a PHP and a JavaScript version available.

The default option

Before I delve into Mustache or Twig I want to make sure I can add support for more template engines without losing the current Handlebars based functionality.
First things first I will add a Theme Customizer setting to allow for a quick and non code-based choice of the template engine to use.
Easy enough I’ve added two more files to the fucntions.php theme file:

<?php

include 'vendor/autoload.php';

include 'src/template-control.php';
include 'src/query-restification.php';
include 'src/rewrites.php';

// theme customizer customizations and additions
include 'src/theme-customizer.php';

// utility template tag functions
include 'src/template-tags.php';

The src/theme-customizer.php file will take care of hiding any Theme Customizer control that has no real effect on the page and to add the only section, control and setting I need:

<?php
add_action( 'customize_register', function ( WP_Customize_Manager $customizeManager ) {

    // remove what we do not need
    $customizeManager->remove_section( 'title_tagline' );
    $customizeManager->remove_section( 'static_front_page' );

    // $customizeManager->remove_panel( 'nav_menus' );
    // We cannot do this!
    // The right way to do it is to filter components on `customize_loaded_components` in a plugin.
    // Since we are in a theme we have to use JS to do it: see the function below.

    // let's add the template setting
    $customizeManager->add_setting( 'templateEngine', [
        'default'   => 'handlebars',
        'type'      => 'theme_mod',
        'transport' => 'refresh',
    ] );

    $customizeManager->add_section( 'rqd_templateEngineSection', [
        'title'    => 'Template Engine',
        'priority' => 30,
    ] );

    $customizeManager->add_control( 'rqd_templateEngineSelect', [
        'label'    => 'Select the template engine to use',
        'section'  => 'rqd_templateEngineSection',
        'settings' => 'templateEngine',
        'type'     => 'select',
        'choices'  => [
            'handlebars' => 'Handlebars',
            'mustache'   => 'Mustache',
            'smarty'     => 'Smarty',
            'underscore' => 'Underscore',
            'twig'       => 'Twig',
        ],
    ] );
} );

add_action( 'customize_controls_print_footer_scripts', function () {
    echo '<script type="text/javascript">jQuery(function(){jQuery(\'#accordion-panel-nav_menus\').hide();});</script>';
} );

While the first function is nothing extraordinary it hints at the nav_menu panel removal: it cannot be “filtered out” from a theme and hence I will rely on JavaScript to hide it.
This is fine with me: the Theme Customizer area requires JavaScript to work and while tapping into it to hide an element is “meh” that’s the only way I could find.
After the clean up the Theme Customizer will look like this
REST Query demo theme Customizer UI

REST Query demo theme Customizer template engine selection
Note that the transport for the setting is set to refresh on purpose: to see the effect of the template engine change the page should be refreshed for PHP initial state rendering to kick in.

What am I seeing?

To make it clear what template engine is being used I’ve modfied the index to show some information and use the new (and only) template tag.
While I’m not pasting the index code here that is visible on the theme repository.
Since Handlebars could not be the engine of choice anymore I’ve created an adapter interface, rqd\TemplateEngineInterface, to abstract and uniform template usage.
The code using the template engine to render the content ares will now assume that contract is honored by any adapter wrapping a real template engine.

// index.php
...
<div id="content-area">
    <?php
    // get hold of the global 'restified' query
    global $wp_query;

    // extract data from each post in the query
    $posts = array_map( function ( $post ) {
        return $post->data;
    }, $wp_query->posts );

    // the `rqdTemplate::templateInclude` method set up the `$templateEngine` var so that we can use it here
    /** @var TemplateEngineInterface $templateEngine */
    $templateContents = $templateEngine->getTemplateContents( 'content' );

    // print the template to the page for the JS version of the template engine to use
    echo '<script type="text/x-handlebars-template" id="tpl-content">', $templateContents, '</script>';

    // render the content initial state with PHP
    echo $templateEngine->render( 'content', [ 'posts' => $posts ] );
    ?>
</div>
...

The adapter interface is exposing the render method with a render($template, array $data) signature similar to what Handlebars does.
See the code here.

Wrapping Handlebars

The concrete adapter implementation for the handlebars.php template engine is quite trivial:

<?php

namespace rqd;

use Handlebars\Handlebars;

class HandlebarsTemplateEngine implements TemplateEngineInterface {

    /**
     * @var Handlebars
     */
    protected $handlebars;

    public function __construct( Handlebars $handlebars ) {
        $this->handlebars = $handlebars;
    }

    /**
     * Returns the template rendered with the specified data.
     *
     * @param string $templateName
     * @param array  $data
     *
     * @return string
     */
    public function render( $templateName, array $data = array() ) {
        return $this->handlebars->render( $templateName, $data );
    }

    /**
     * Returns the string content of the specified template.
     *
     * @param string $templateName
     *
     * @return string
     */
    public function getTemplateContents( $templateName ) {
        return $this->handlebars->getLoader()->load( $templateName )->getString();
    }
}

And the one needed for Mustache will not differ that much either.

Next

I will generalize the JavaScript side of the code, now tightly coupled with Handlebars.js and implement the first other engine support: Mustache.

I appreciate your input