JavaScript Video Tutorial: Visual Page Editor
When creating a content management system, page editing is always a complex step. This is a problem I encountered while working on Boxraiser (a Shopify-like platform, but specialized in subscription in addition to traditional e-commerce). The site had a page editor working with tinyMCE and the bootstrap module but the ergonomics were not obvious and creating complex pages was impossible.
Also, as part of the redesign I wanted to improve the page editing experience and I embarked on creating an open source visual page editor for the occasion.
Why a new editor?
Before embarking on such an adventure I considered other solutions but I was not necessarily satisfied with what existed.
- TinyMCE, CKEditor or Editor.js are visual editors centered around rich text editing. They are very effective for editing content (article) but they are not viable solutions for creating complete pages.
- Craft.JS is a visual page editor but offers too much control over elements. This approach requires some sensitivity on the part of the administrator to create consistent pages.
my solution
When I was working on WordPress sites one approach that worked well was to prepare blocks upfront and then let users manage the content of those blocks using plugins like ACF. My goal was therefore to reproduce a similar experience but with an editor that does not depend on a framework or a CMS.
So the solution was to create the editor I needed and with Boxraiser’s agreement we decided to make it open source. Also, I am pleased to introduce @boxraiser/visual-editor!
A “data-first” approach
The objective of the editor is therefore to separate the content (which will be administered by the user) and the design which will be managed by the developer. The administrator is offered the possibility to select the blocks to be used in his page and to choose the associated content. Let’s take a concrete example with a banner:
We start by saving our block with the fields that we want to be able to manage.
import { VisualEditor, HTMLText, Repeater, Text, Row, Select, Range } from '@boxraiser/visual-editor'
let editor = new VisualEditor()
editor.registerComponent('hero', {
title: 'Hero',
category: 'Banner',
fields: [
Text('title', {multiline: false}),
HTMLText('content'),
Repeater('buttons', {
title: 'Boutons',
addLabel: 'Add a new button',
fields: [
Row([
Text('label', { label: 'Label', default: 'Call to action' }),
Text('url', { label: 'Link' }),
Select('type', {
default: 'primary',
label: 'type',
options: [
{ label: 'Primaire', value: 'primary' },
{ label: 'Secondaire', value: 'secondary' }
]
})
])
]
}),
Range('padding', {
label: 'Espacement',
default: 5,
max: 5
})
]
})
// On enregistre le custom element
editor.defineElement()
Then, we can use our editor wherever we want using the custom element <visual-editor>
<visual-editor
name="content"
preview="/preview"
iconsUrl="/assets/editor/[name].svg"
value="[]"
></visual-editor>
You can use the attribute hidden
to manage its visibility
The field will behave like a textarea and can be used in a classic form. The data will be sent as a JSON object array containing the field values with an additional attribute _name
containing the name of the block used.
[
{
"title": "Album example",
"titleAlign": "center",
"content": "<p>Something short and leading about the collection below—its contents, the creator, etc. Make it short and sweet, but not too short so folks don't simply skip over it entirely.</p>",
"buttons": [
{
"label": "Main call to action",
"url": "#",
"type": "primary"
},
{
"label": "Secondary action",
"url": "#",
"type": "secondary"
}
],
"padding": 5,
"_name": "hero",
}
]
It is then up to you to use this data to build your page.
Preview
What would a page editor be without a visual preview of the page? For this preview I wanted to avoid having to do the duplicate work so the server will take care of rendering the preview. When you first open the editor the URL passed in the attribute preview
will be called in POST
and will receive all data from the publisher.
curl 'https://preview.url/' \
--data-raw $'[<BLOC_DATA>]' \
--compressed
The rendered page must be a valid HTML document that must respect the following rules:
- An element with the id
ve-components
will have to surround the rendered blocks. - Each block should be a direct child of this
#ve-components
and have only one root element (this allows the editor to find the blocks in the preview).
Then when a block is modified, a call to the preview URL is made with only the data of the modified block
curl 'https://visual-editor.droapp.com/' \
--data-raw $'{"title":"Album example","_name":"hero","_id":"0","preview":true}' \
--compressed
The server will then only have to render this block in HTML format. The editor will take care of injecting this new block into the page preview.
See you on the doc
If you want to learn more I invite you to go to the documentation.