How to add dynamic content to Jigsaw
We've been working on a CMS product for a while.
One need that we had was to mix statically rendered pages (flat HTML) with dynamic content (pulling from a database). We really liked the workflow and power of building sites with Jigsaw. But, when it came to rendering dynamic content, we needed to create our own solution.
This is how we did it.
- Create placeholders that can be adjusted after Jigsaw compiles content down to HTML.
- Using Jigsaw's Event Listeners, swap out the placeholders with PHP.
- Convert all files from
.html
to.php
so that the server will interpret the files correctly.
Step 1: Create placeholders
In our blade files, we add a custom tag anywhere we want to inject dynamic content. That tag looks like this:
<?php $settings = $block->settings; ?>
<cms-content type="special-content" settings='@json($settings)' />
Here, we're pulling just a few settings from the CMS and using blade to create a JSON object we can pass along to PHP. The type
attribute is arbitrary and eventually references a PHP file that will be responsible to render dynamic content.
Step 2: Use Jigsaw's Event Listeners
After all of our pages compile, we run each of them using the afterBuild
Event Listener that Jigsaw makes available. Event Listeners go in a bootstrap.php
file in the root of your Jigsaw project. Here's ours.
In the Replacer
file, we do a few things:
- Loop through the pages collection
- Process the content of each page
- Convert the files from
.html
to.php
Here's (most) of our Replacer
file
<?php
namespace App\Listeners;
use TightenCo\Jigsaw\Jigsaw;
class Replacer
{
protected $jigsaw;
public function handle(Jigsaw $jigsaw)
{
$this->jigsaw = $jigsaw;
$this->rewriteToPhp();
}
public function rewriteToPhp() {
$jigsaw = $this->jigsaw;
$basePath = $jigsaw->getDestinationPath();
collect( $jigsaw->getOutputPaths() )->each(function ($path) use ($jigsaw, $basePath) {
if (! $this->isAsset($path)) {
$content = $jigsaw->readOutputFile($path . '/index.html');
// Process the content and add helpers to the top of the file
$updatedContent = $this->cmsHelpers() . PHP_EOL . $this->processContent($content);
// Delete the HTML file and rename it to .php
unlink( $basePath . $path . '/index.html');
$jigsaw->writeOutputFile($path . '/index.php', $updatedContent);
}
});
}
public function isAsset($path)
{
return starts_with($path, '/assets');
}
private function processContent($content)
{
// Replace content
$pattern = '|<cms-content\s+(?:type="(?<type>[\w-]*)")?\s+(?:settings=\'(?<settings>.*)\')?\s+\/>|';
$content = preg_replace_callback( $pattern, function($matches) {
$type = $matches['type'];
$settings = json_encode($matches['settings']);
return $this->cmsContent($type, $settings);
}, $content);
return $content;
}
public function cmsContent($type, $settings)
{
return
<<<CMSCONTENT
<?php \$settings = $settings;?>
<?php require('helpers/{$type}.php'); ?>
<?php \$settings = null;?>
CMSCONTENT;
}
}
The biggest things to note about this file are:
- The use of
preg_replace_callback
to replace<cms-content />
throughout our rendered pages - How we pass settings to each helper file that actually renders the dynamic content.
Our server has a centralized helpers
project that actually contains the files that are required through the replaced content. Those files are where we fetch remote, dynamic content on page load.
Thanks Jigsaw!
By providing various events (like beforeBuild
and afterBuild
), Jigsaw has made this type of workflow incredibly easy. Although it took us a bit of time to think outside of our own box, we're very happy with the final solution.