Universal Editor for Headless CMS: Part 2, Layout

Anton Tishchenko
Anton Tishchenko
Cover Image for Universal Editor for Headless CMS: Part 2, Layout

We decided to build a visual editor that will work with almost any headless CMS. The first big question is about saving the presentation data: Where and how can we save what will be on the site?

Location

One possible option to save presentation information is somewhere inside CMS. We can extend our page content model with a field named “Layout” or ”Presentation” and save data there. The pros are that we don’t introduce any big requirements. However, the cons are that not all pages have a corresponding content item with the headless approach. But that should not be a very big problem or task: just a simple content model and a few lines of code to get fields from that content. Another disadvantage is that we “break” the true headless principle: your data should not be stored together with a presentation. But that will not be a big “break”. All headless vendors still break this rule with their “studio” products.

Another option is to use some 3rd party storage. It can be any custom service or database. This option is more flexible. However, it adds dependency. We want to have as few dependencies as possible. Our ideal picture is to have: a CMS instance(hosted or cloud), a static website instance(hosted as static HTML), a preview website instance, and an editing website instance. We should avoid anything extra.

Format of data

HTML is hierarchical. All that we can see on websites builds from <html>, <body> down to smaller and smaller parts of pages. All modern web frameworks also use hierarchical code to make the page. The two most popular hierarchical formats are JSON and XML. The JSON support as a field type in headless CMSs is much wider compared to XML. Also, we will run our code in a browser context. And JavaScript support of JSON.parse makes our choice obvious. We should go with JSON.

JSON structure

We want to save two main things in our JSON:

  • Components information: name, data source, presentation settings(additional CSS).
  • Places, where components are inserted

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton

I tend to name these things rendering and placeholder due to my Sitecore experience.

But, there are many names for parts of pages in different CMSs:

  • Widgets in dotCMS
  • Widgets and Web Parts in Kentico
  • Blocks in Drupal
  • Widgets in WordPress
  • View Components, Partial Views, and MVC Views in Umbraco
  • Renderings, Sublayouts in Sitecore
  • Blocks in Episerver

And many names for places, where content could be inserted:

  • Containers in dotCMS
  • Zones in Kentico
  • Regions in Drupal
  • Widget Areas in WordPress
  • Content Placeholder in Umbraco
  • Placeholder in Sitecore

Also, there is a similar concept to places, where other components should be inserted in web frameworks. Vue.js uses the term Slots , which I like a lot.

We have too many names for similar concepts in different CMSs. Yes, these concepts also differ from one CMS to another. But the idea remains the same, we have a place, where content could be inserted. And we have parts of code that could be inserted into the page. Another important thing is trying to avoid the usage of the same term, that is used for different things in CMS.

Let’s stop on component and zone as working names for now. They are good enough to describe the meaning even without any documentation.

Layout JSON could look like this:

{
    "zones": [
        {
            "name": "header",
            "components": [
                {
                    "name": "menu",
                    "datasourceType": "none",
                    "datasourceId": "",
                    "zones": []
                },
                {
                    "name": "breadcrumb",
                    "datasourceType": "none",
                    "datasourceId": "",
                    "zones": []
                }
            ]
        },
        {
            "name": "main",
            "components": [
                {
                    "name": "hero",
                    "datasourceType": "hero",
                    "datasourceId": "0001",
                    "zones": []
                },
                {
                    "name": "twoColumns",
                    "datasourceType": "none",
                    "datasourceId": "",
                    "zones": [
                        {
                            "name": "left",
                            "components": [
                                {
                                    "name": "text",
                                    "datasourceType": "promo",
                                    "datasourceId": "0002",
                                    "zones": []
                                },
                                {
                                    "name": "text",
                                    "datasourceType": "promo",
                                    "datasourceId": "0003",
                                    "zones": []
                                }
                            ]
                        },
                        {
                            "name": "right",
                            "components": [
                                {
                                    "name": "text",
                                    "datasourceType": "promo",
                                    "datasourceId": "0004",
                                    "zones": []
                                },
                                {
                                    "name": "text",
                                    "datasourceType": "promo",
                                    "datasourceId": "0005",
                                    "zones": []
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

We will save it in the JSON field in the CMS and use it during rendering the page.

More on this in future series, stay tuned.