Custom Grid Editors
Learn how to create a Custom Grid Editor in Umbraco Heartcore.
The grid editor Data Type in Heartcore is deprecated and will be retired in June 2025 or thereafter. For more information read the following blog post.
In this tutorial, we will create a Custom Grid Editor using custom elements and Lit. We will look at how we can define what the API response for our data should look like.
Content
Create a Document Type and grid configuration
First, we will need to create a Document Type containing the Grid Layout property editor.
Go to the Settings section.
Create a new Document Type called Grid Page.
Add a new property called Grid Content.
Select the Grid layout editor.
Accept the default configuration.
The Document Type should now look like this:
To allow the Document Type to be created in the tree we need to change the permissions:
Select the Permissions tab.
Ensure Allow as root is checked.
To verify the configuration follow these steps:
Go to the Content section.
Create a new page based on the Grid Page Document Type we created.
Give it a name.
Save the page.
Try choosing a layout, add a row and click Add content. Here we will see that we have a couple of editors to choose from.
Try adding one of them to the page to see how they work.
A look at a default grid editor
Before we start writing our own Grid Editor, let's have a look at the Headline Grid Editor.
As we can see the page is divided into two sections; one with a Text Editor with the code for the editor and one with a preview of the editor.
Let's take a look at the code.
A custom editor inherits from HTMLElement
and must be the default export.
A couple of private fields are defined. One for storing a reference to the textarea
field and one for the HTML template.
In the constructor we set the inner HTML of our custom element to our template and set the #textarea
field to a reference to the textarea.
The click
method is called when the grid control is clicked. In this case we use it to set focus on the textarea.
A value
property is needed for the value to be stored when a page is saved and to set the value when the editor is loaded.
In this example the value property is setting and returning the value of the textarea.
Create a custom grid editor
For this tutorial we will create an image gallery editor using Lit. Lit builds on top of the Web Components standards and helps us avoid a lot of boilerplate code.
Go to the Settings section.
Create a new Grid Editor.
Choose an icon.
Name it Image Gallery.
Change the alias to
my-image-gallery
.
The alias is used as the custom element tag name and must be unique. By choosing a prefix that is less likely to used by any other HTML element, in this case my-
, there is less chance for running into conflicts.
In the JS
view we can see there is already some boilerplate code. Let's replace it with the following:
The preview should now look like this:
Let's break down the code.
Here we're importing LitElement
, CSS and HTML from the Lit library.
We're exporting a default class inheriting from LitElement
.
We tell Lit that we have a value
property which we expect to be an Array
.
We define the styles in a static field called styles
. This will make Lit automatically inject a stylesheet into our custom element. The text returned needs to use the css
tag from Lit
. For more info see the Lit styles documentation.
The render method returns the HTML we want to show. It needs to be tagged with html
. For more info see the Lit templates documentation.
Let's save the editor and see how it looks when adding it to the a content item.
We now have the Add image button rendering, but clicking it does nothing. Let's do something about that.
Go back to the Settings section.
Click on the Image Gallery Grid Editor.
We will start by adding the following to the top of the file:
This imports the mediaPicker
function from the backoffice bridge library.
We will add another function before the render
function:
This will open the Media Picker with a configuration that only allows selecting images and showing the detail view, which allows us to enter an alt text, caption and choose a focal point.
The submit callback is called when the Submit is clicked. It will contain an array of the selected items. In this case it will contain a single item, but if we set multiple
to true
multiple items can be returned.
In our callback method we check if this.value
has been initialized. If it has not we initialize a new array. We then push the selected image to the array. Note that we also store the udi
(the id of the media) in a url
property. We will come back to why we do that later.
Since we add to the array and do not set the value
property we need to tell Lit that the property was updated. We do this by calling this.requestUpdate('value')
.
We will also need to add a click
event to the button so it will show the dialog:
Clicking the button in preview mode does nothing. This is because of a limitation when previewing where we do not have access to the full backoffice. Saving and testing on a Content item should work.
Click Save.
Go back to the content page.
Click the
Add image
button.
We should now see the image overlay.
Selecting an image does not currently render anything. Let's do something about that. Go back to the grid editor code in the Settings section.
Update the render()
function with the following
We assign the editor value to a local variable items
, if this.value
is null
or undefined
we assign an empty array. Then in the HTML we are now looping over the items and returning an umbh-image
for each item. umbh-image
is a custom element included with the backoffice bridge, it takes an udi
and renders an images based on that.
Go back to the Content section try to add some images to a page using the editor.
We now have an editor that we can add images to which gets rendered in the backoffice. Next step would be to add edit and delete functionality, but we will leave that to the reader.
Using module aliases
Up until now we have been using the full URL when importing third party modules into our editors. While this works fine most of the time, we will most likely run into issues when importing libraries that registers custom elements. This is because custom elements needs to be registered using an HTML tag and a tag can only be registered once.
Imagine we have an editor using custom elements from a third party library. We then create another editor using the same library but in a newer version which adds some new functionality to the custom element we use.
One of two things will happen:
The library checks if a custom element with the same name has already been registered and skips registration. This will leave us not knowing which version is used and might break our implementation.
It will try to register the element with the same name but fail because there is already an element registered with the same name.
In either case we will end up with a broken editor experience. This is where module aliases comes in.
Module aliases allows us to define a common name for a module that we can use in our editors instead of the full URL. A module alias can be defined by going to the Headless -> Custom Editor Configuration page in the Settings section.
Here we have defined an alias @headless-backoffice-bridge
pointing to a Content Delivery Network (CDN) URL of the backoffice bridge library.
With this alias added we can update our import in our editor to:
The alias will automatically be replaced with the URL we have defined, similar to how node
does.
By using the same alias in all our editors, we can ensure that only one version of the library is loaded. If we want to upgrade to a newer version, we will only need to update the URL in one place.
Describing the grid editor using JSON schema
Now that we have a working editor, let's add a couple of images to our page. When done, click Save and publish.
Note down the Id from the Info tab. We will need that in a bit.
Go to the Settings section.
Expand the Headless node.
Open the API Browser
Type in
https://cdn.umbraco.io/content/<CONTENT_ID>
in the Explorer URL bar, replacing<CONTENT-ID>
with the id we copied before.Click Go!.
In the output we can see the JSON we have stored in the editor.
Notice our value stored is returned as a string
. This makes it hard to consume, since we will need to parse it as JSON everywhere we use that value. This is where JSON schemas comes in handy.
Let's go back to our Grid Editor implementation and see what we can do.
Looking at the default JSON schema we can see that the type is set to string
. This tells Heartcore that our editor is returning its data as a string
. This is what we saw before in the API response. Since we are storing an array of images, let's change the type
to array
. We also need to specify what the array is containing. We do that by adding an items
property containing a type: object
property.
Change the
type
fromstring
toarray
Add an
items
property containing atype: object
property.
The schema should now look like this:
Let's go back to the API Browser, type the same URL as before and click Go!.
Inspecting the output, we can already see an improvement. The value is now returned as an array which is already much better.
Remember the url
property stored earlier? Let's make it return a URL instead of a UDI.
Go back to the Grid Editor schema.
Update it to the following:
With the JSON above we are adding a properties
object containing a url
property. This property has its type
set to string
, and most importantly, format
set to uri-reference
. By specifying the format
as uri-reference
Heartcore will try to parse the value as a UDI and if the UDI is for either a document or media, the URL of that item will be returned instead. We can see that if we go back to the API Browser.
We now have an array where each item has the URL to the picked media item.
While it is not necessary to define all properties in the JSON Schema, it is highly recommended as the schema is also used for validating the editor data when saving.
Besides the uri-reference
format there's also rich-text
. This is useful when storing rich text data like the output from the TinyMCE editor. When specifying the rich-text
format, things like {locallink}
and data-uri
will automatically be replaced with the correct URLs.
Related articles
Last updated