Implementing the Preview Protocol
After learning how real time content previews work in Mattrbld and what constraints and limitations you should be aware of, you will learn how to implement the preview protocol in your own project in this article. At the end of it, you should have a fully working real-time preview of your content right in the CMS.
Prerequisites
For the preview to work, you need a dedicated route for it in your project. A common convention is to have it under https://example.com/___preview/
, but you can place it wherever you want. This route needs to be accessible by your Mattrbld instance of choice and embeddable into an iFrame. It will host an HTML document that will be used to render the content passed from Mattrbld. This is your preview page.
It’s recommended to use a web-server with live-reloading while you’re developing the preview page, so you can immediately see the changes you make reflected in Mattrbld. Preview-URLs running under localhost
are supported.
Once a Preview-URL was added in the Project Settings, the content editor of any collection will show a “Show Preview” button in the top toolbar next to the “Save” button.
As of version 0.4.0
you can manage the availability of previews on a per-collection basis. If the “Show Preview” button doesn’t show up for you, it may be because the previews were disabled for the Collection the item you’re editing is in.
Clicking this button will change the content area to a split-screen view, with your content on the left and the preview on the right. The preview area is an iFrame loading your custom preview page.
Once everything is implemented correctly, changing the content will update the preview. However, a bidirectional connection between Mattrbld and the website running in the preview pane will have to be established first. This is made possible through the window.postMessage
method. You can learn more about it here.
The Handshake
As soon as the page in the preview pane has loaded, Mattrbld sends a first postMessage
to it containing the following data:
{
handshake: 'p7s8n1c', // random 7 character code
}
It then waits for a return of this handshake and, optionally, an Array of extra features the preview page implements. To handle this handshake and establish a connection, you can add the following code to your preview page:
// replace this with the URL of your Mattrbld instance if you're using a custom one!
const MATTRBLD_INSTANCE = "http://app.mattrbld.com/";
const cachedSource = null;
function handleMessage(e) {
// get the relevant details from the message
const { origin, data, source } = e;
if (origin !== MATTRBLD_INSTANCE) {
// handle postMessages from other origins
// usually by throwing an error
return;
}
// cache the message source to send data back
cachedSource = source;
// handle the handshake
if (data && data.handshake) {
cachedSource.postMessage(
{
handshake: data.handshake,
// include optional features here
// but only if implemented!
supports: [],
},
MATTRBLD_INSTANCE
);
}
}
// handle messages coming from Mattrbld
window.addEventListener('message', handleMessage);
// needed to signal Mattrbld that the page has loaded if it was opened in a separate tab / window
if (window.opener) {
window.opener.postMessage(
{ loaded: true }, MATTRBLD_INSTANCE
);
}
As of version 0.4.0
, Mattrbld supports the optional preview features, which require extra methods to be implemented in the preview page. To activate them, you can pass their names in the supports
-Array. To keep this example simple, it implements only the required features, hence why the array is empty.
With this code in place, clicking the “Show Preview” button should open the preview, show a blank page, but also the following message: “Communication with the preview has been set up successfully”.
With that, the preview page is ready to receive actual data, which Mattrbld will start sending immediately.
Preview Data
The general preview data Mattrbld sends after the handshake and after every change to the content, has the following format:
{
// the name of the collection the content is in
collection: 'Pages.json',
// the URL of the content item
url: '/docs/implementing-the-preview-protocol/',
// the content according to the Schema
data: {…},
// any Media Library images
imageMap: <Map>
}
The url
property will be compiled based on a number of factors:
Whether the Collection specifies a URL-template
Whether it is localised
Whether the content item is a draft or not
If these conditions aren’t met, the URL will instead be assembled based on the path within the project. In any case, depending on your build-process, the URL might not reflect the final, published URL.
The data
property generally contains all the data of your content according to the Schema assigned to the content item. This is what you will use to actually render the preview for your project.
Handling Messages from Optional Features
Mattrbld content previews support additional features, such as comments. These are optional, since they require extra implementations on in the preview page and may not be applicable to all types of projects. You can find implementation details about these features in their respective documentation articles.
If these features are enabled, Mattrbld will send additional information depending on the feature. To distinguish these messages from regular content updates, they include a feature
property with the name of the feature:
// inside the handleMessage() function
if (data && data.handshake) {
// handle handshake
} else if (data && data.feature === 'comments') {
// handle comment-feature related messages e.g.:
switch (data.type) {
case 'modechange':
// handle modechange event
break;
default:
break;
}
} else if (data) {
// render content
}
Rendering Strategies
Rendering the data you receive from Mattrbld onto your preview page is up to you—and the primary reason why this preview system is so flexible. The complete example below will simply render the data as JSON into a <pre>
element, which allows you to clearly see the data that is passed. This obviously isn’t very useful beyond demoing the protocol, but here are some pointers of other ways you could render the data:
If you’re building a website with a component-based framework such as Svelte, Vue, or React, you could render the page as you would in the rest of the application, but using the
collection
property to decide which component should be handling the dataIf you’re building a simple blog using Markdown files, you could transform the data into Markdown using a client-side Markdown-parser and inject the HTML into an output element that’s embedded into your normal post layout on the preview page
You could use a client-side templating engine to render the data using templates and display the resulting HTML
In the end, the only limit is your imagination! (Well, and the capabilities of the browser—but browsers these days are very powerful.)
A Complete Example
To help you get started, here is a complete (albeit basic) implementation of the preview protocol you can use as your preview page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Preview Protocol Tester</title>
</head>
<body>
<h1>Mattrbld Preview Protocol Tester</h1>
<pre>Ready for output.</pre>
<script>
// replace this with the URL of your Mattrbld instance if you're using a custom one!
const MATTRBLD_INSTANCE = "http://app.mattrbld.com/";
const output = document.body.querySelector('pre');
function handleMessage(e) {
// get the relevant details from the message
const { origin, data, source } = e;
if (origin !== MATTRBLD_INSTANCE) {
output.innerText = `The message came from an unallowed origin: ${origin}`;
return;
}
// handle the handshake
if (data && data.handshake) {
source.postMessage(
{
handshake: data.handshake,
supports: [],
},
MATTRBLD_INSTANCE
);
} else {
// Render the received data
output.innerText = JSON.stringify(data, null, 2);
}
// handle messages coming from Mattrbld
window.addEventListener('message', handleMessage);
// needed to signal Mattrbld that the page has loaded if it was opened in a separate tab / window
if (window.opener) {
window.opener.postMessage(
{ loaded: true }, MATTRBLD_INSTANCE
);
}
</script>
</body>
Since we are not converting the imageMap
property to anything, it will show up as {}
when displayed with JSON.stringify()
. This is intentional in this example, since it contains binary Blobs
that wouldn’t translate well to being displayed as text. In your own implementation, you may want to convert the imageMap
to ObjectURLs in order to display them as images, as described in the constraints and limitations article.
In this article, you learned how to create a preview page that you can link to from your Mattrbld project to enable real-time previews for your content. The next articles will teach you how to implement specific optional features, such as comments. If you’re not interested in them, you can instead skip to the final article of this section, where you will learn about some common issues with the preview functionality and how to fix them.