Skip to main content

Create a New Page Element

What you'll learn
  • how to create a new Page Builder element
  • how to register plugins in Webiny applications

Overview#

Although the Page Builder application comes with a plethora of ready-made page elements, at one point in time, you might need to create your own to satisfy your specific requirements. To achieve that, we can utilize a couple of simple plugins, which is what we'll cover in this short tutorial.

What We'll Build#

We’ll create a new page element that allows content creators to embed content using an iframe. Here's what the final result will look like:

Iframe page element
info

The full code example used in this tutorial is available in our webiny-examples Github repository.

Organization of Files#

Although you can place and organize the plugins in any way you prefer for your project, we recommend adopting the following organization.

When creating new page elements, it's recommended to have all of the needed plugins in a single shared folder. This is because some of the plugins will need to be imported both within the Admin Area (apps/admin) and Website (apps/website) applications.

With that in mind, for this tutorial, we will create the new pb-element-iframe folder in the apps/extensions folder.

note

By default, the extensions folder does not exist, so you'll need to create it.

Anatomy of Element Plugins#

To create a new page element, we need to register two sets of plugins:

  1. For the Admin Area application:
  • a plugin that defines how it's rendered in the page editor
  • a plugin that defines all the available settings and options for it
  1. For the Website application:
  • a plugin that defines how the page element is rendered on the actual page

To better organize files, we'll write all the plugins needed for the Admin Area application and Website application in apps/extensions/pb-element-iframe/admin and apps/extensions/pb-element-iframe/render, respectively

Let's get started with scaffolding initial folders and files for our page element plugins.

Scaffold Folder and Files for Plugins#

apps/extensions/pb-element-iframe/package.json
{
"name": "@extensions/pb-element-iframe",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@emotion/styled": "10.0.27",
"@webiny/app-page-builder": "5.12.0",
"@webiny/ui": "5.12.0",
"@webiny/validation": "5.12.0",
"classnames": "^2.2.6",
"emotion": "10.0.27",
"react": "^16.14.0"
}
}
apps/extensions/pb-element-iframe/tsconfig.json
{
"extends": "../../../tsconfig",
"include": ["."],
"compilerOptions": {
"composite": false
}
}
  • add "apps/extensions/pb-element-iframe" to workspaces packages inside package.json in project root

  • create admin and render folders inside apps/extensions/pb-element-iframe

  • link workspaces by running yarn install command from project root. After completion there will be a symbolic link of @extensions/pb-element-iframe package in node_modules

  • add @extensions/pb-element-iframe package as a dependency to both the Admin Area application and Website application, by editing the apps/admin/code/package.json and apps/website/code/package.json files respectively:

"@extensions/pb-element-iframe": "^1.0.0",

After successfully performing the above-mentioned steps, the folder structure of our project will look similar to this:

// Config files are omitted for sake of brevity.
β”œβ”€β”€ api
β”œβ”€β”€ apps
β”‚Β Β  β”œβ”€β”€ admin
β”‚Β Β  β”œβ”€β”€ extensions
β”‚Β Β  β”‚Β Β  └── pb-element-iFrame
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ admin
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ render
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ package.json
β”‚Β Β  β”‚Β Β  └── tsconfig.json
β”‚Β Β  β”œβ”€β”€ theme
β”‚Β Β  └── website
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.build.json
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ webiny.project.ts
└── yarn.lock

Create Plugins for the Admin Area Application#

Click the plus icon located on the left sidebar of the editor to access all the available page elements:

Iframe page element

As mentioned, this list of page elements can be expanded and custom page elements can be created via plugins. We will add the necessary plugins for Admin Area application in the apps/extensions/pb-element-iframe/admin/index.tsx file.

Editor Element Plugin#

We will start with the PbEditorPageElementPlugin plugin:

apps/extensions/pb-element-iframe/admin/index.tsx
// ...
// Some code is removed for sake of brevity.
// ...
import React from "react";
import { PbEditorPageElementPlugin, DisplayMode } from "@webiny/app-page-builder/types";
import { createInitialPerDeviceSettingValue } from "@webiny/app-page-builder/editor/plugins/elementSettings/elementSettingsUtils";
import { ReactComponent as IFrameIcon } from "./assets/iframe-icon.svg";
import IFrameEditor from "./components/IFrameEditor";
export default () => {
return [
{
name: "pb-editor-page-element-iframe",
type: "pb-editor-page-element",
elementType: "iframe",
toolbar: {
// We use `pb-editor-element-group-media` to put our plugin into the Media group.
title: "iFrame",
group: "pb-editor-element-group-media",
preview() {
return (
<PreviewBox>
<IFrameIcon />
</PreviewBox>
);
}
},
settings: [
"pb-editor-page-element-settings-delete",
"pb-editor-page-element-style-settings-height"
],
target: ["cell", "block"],
onCreate: "open-settings",
create(options) {
/*
Create function is here to create the initial data
for the page element, which then is utilized in the
IFrameEditor component and in the element settings.
*/
return {
type: "iframe",
elements: [],
data: {
iframe: {
// The URL property will be populated when user enters the URL in the element settings.
url: "",
},
settings: {
height: createInitialPerDeviceSettingValue(
{ value: "370px" },
DisplayMode.DESKTOP
)
}
},
...options
};
},
render(props) {
/*
Every render function receives the page element's
data assigned to the "element.data" property in
the received props. In here we will store the
"iframe.url" which will be provided via the page
element's settings.
*/
return <IFrameEditor {...props} />;
},
renderElementPreview({ width, height }) {
return <img style={{ width, height }} alt={"iFrame"} />;
}
} as PbEditorPageElementPlugin
];
};

The key properties of the plugin are the create and render. They define the initial page element's settings and how it will be rendered in the editor, respectively.

The data property holds the initial state of the page element which can contain any data you might need.

The toolbar property helps us put our plugin into the toolbar.

Finally, it’s up to the render function to define how the page element will be rendered once the user drops it on the page.

note

Notice the props that were passed to the render function. This object contains all the relevant page element's data and settings.

After registering the plugin in the Admin Area application the new page element should appear in the editor toolbar, similar to the pre-defined elements as shown below:

Editor Iframe page element

Now that we've covered the overall structure of the plugin, let's see what's inside the IFrameEditor component:

apps/extensions/pb-element-iframe/admin/components/iframeEditor.tsx
// ...
// Some code is removed for sake of brevity.
// ...
const IframeEditor = props => {
const { element } = props;
if (!element.data.iframe.url) {
return (
<PreviewBox>
<IFrameIcon />
</PreviewBox>
);
}
return (
<ElementRoot
className={
"webiny-pb-base-page-element-style webiny-pb-page-element-iframe " + outerWrapper
}
element={element}
>
<iframe src={element.data.iframe.url} width="100%" height="100%" />
</ElementRoot>
);
};
export default IframeEditor;

As you can see, it's a simple React component which renders an iframe HTML element.

note

Every element needs to have the ElementRoot component as root because it applies styles, classes and attributes to the element assigned via right sidebar style tab.

info

Notice we've added webiny-pb-base-page-element-style CSS class. Every element needs to have it as it contains all base styles applied by the Page Builder application.

tip

Notice we've added webiny-pb-page-element-iframe CSS class, which enables us to add custom CSS styling if needed.

Element Settings Plugin#

The next plugin we'll need to define is the PbEditorPageElementAdvancedSettingsPlugin, which we will use to render the required UI for settings, so the user can provide an iframe URL. The settings UI will be shown automatically in the right sidebar when the user drag-and-drop the page element on the page.

We will add this plugin in the apps/extensions/pb-element-iframe/admin/iframeSettings.tsx file:

apps/extensions/pb-element-iframe/admin/iframeSettings.tsx
// ...
// Some code is removed for sake of brevity.
// ...
export default {
name: "pb-editor-page-element-advanced-settings-iframe",
type: "pb-editor-page-element-advanced-settings",
elementType: "iframe",
render({ Bind, submit }) {
return (
<Accordion title={"IFrame"} defaultValue={true}>
<React.Fragment>
<Bind name={"iframe.url"} validators={validation.create("required,url")}>
<Input label={"URL"} description={"Enter an iFrame URL"} />
</Bind>
<Grid className={classes.simpleGrid}>
<Cell span={12}>
{/* @ts-ignore */}
<ButtonContainer>
{/* @ts-ignore */}
<SimpleButton onClick={submit}>Save</SimpleButton>
</ButtonContainer>
</Cell>
</Grid>
</React.Fragment>
</Accordion>
);
}
} as PbEditorPageElementAdvancedSettingsPlugin;

As mentioned, this code will show the settings UI in sidebar and ask for the URL of the iframe, as shown below:

Iframe page element settings

Render Element Plugin#

As mentioned, every page element consists of two sets of plugins. We've already covered the editor side of the logic, now, let's write the plugin that will render it to the page preview and the actual page.

In order to render the element on the actual page, we will use the PbEditorPageElementPlugin plugin.

We will add this plugin in the apps/extensions/pb-element-iframe/render/index.tsx file.

apps/extensions/pb-element-iframe/render/index.tsx
import React from "react";
import { PbRenderElementPlugin } from "@webiny/app-page-builder/types";
import IFrame from "./components/iFrame";
export default () =>
({
name: "pb-render-page-element-iframe",
type: "pb-render-page-element",
elementType: "iframe",
render({ element }) {
return <IFrame element={element} />;
}
} as PbRenderElementPlugin);

The following code snippet shows the IFrame component:

apps/extensions/pb-element-iframe/render/components/iFrame.tsx
import { ElementRoot } from "@webiny/app-page-builder/render/components/ElementRoot";
//...
// Some code is removed for the sake of brevity.
//...
const IFrame = ({ element }) => {
const { data } = element;
// If the user didn't enter a URL, let's show a simple message.
if (!data.iframe.url) {
return <div>IFrame URL is missing.</div>;
}
// Otherwise, let's render the iframe.
return (
<ElementRoot
className={
"webiny-pb-base-page-element-style webiny-pb-page-element-embed-iframe " +
outerWrapper
}
element={element}
>
<iframe src={data.iframe.url} width="100%" height="100%" />
</ElementRoot>
);
};
export default IFrame;

Along with the actual page, this plugin will also responsible for rendering the element in the page preview, as shown in the image below:

Page preview inside editor

Register Plugins in the Admin Area Application#

Now that we've created plugins for the iframe element, we need to register them in the Admin Area application to work. Let's make the following changes in apps/admin/code/src/plugins/pageBuilder/editorPlugins.ts file:

apps/admin/code/src/plugins/pageBuilder/editorPlugins.ts
// Some code is removed for the sake of brevity.
(...)
// Import the `iframe` element and it's settings
import iframeElement, { iframeSettings } from "@extensions/pb-element-iframe/admin";
export default [
iframeElement(),
iframeSettings,
// Rest of the plugins
(...)
];

Also in apps/admin/code/src/plugins/pageBuilder/renderPlugins.ts file:

apps/admin/code/src/plugins/pageBuilder/renderPlugins.ts
// Some code is removed for the sake of brevity.
(...)
// Import the `iframe` element
import iframeElement from "@extensions/pb-element-iframe/render";
export default [
// Elements
iframeElement(),
// Rest of the plugins
(...)
];

After registering the plugins successfully, we should be able to see the iframe element in the Admin Area application, as shown below:

Editor Iframe page element

Add Plugins in the Website Application#

Render Element Plugin#

In order to render the page element in the Website application, we will use the PbRenderElementPlugin plugin again.

apps/extensions/pb-element-iframe/render/index.tsx
import { PbRenderElementPlugin } from "@webiny/app-page-builder/types";
import IFrame from "./components/iFrame";
export default () =>
({
name: "pb-render-page-element-iframe",
type: "pb-render-page-element",
elementType: "iframe",
render({ element }) {
return <IFrame element={element} />;
}
} as PbRenderElementPlugin);

Register Plugins in the Website Application#

Make the following changes in the apps/website/code/src/plugins/pageBuilder.ts file:

apps/website/code/src/plugins/pageBuilder.ts
// Some code is removed for the sake of brevity.
(...)
// Import `iframe` element
import iframeElement from "@extensions/pb-element-iframe/render";
export default [
(...)
// Page elements
iframeElement(),
(...)
];

After registering the plugins successfully, the page element should be visible in your Website application:

Page preview inside editor

Conclusion#

In this tutorial, we’ve learned how we can create a new iframe page element and make it configurable within the Page Builder application’s page editor. The same approach can be used to create any page element, so make sure to bookmark and revisit this tutorial, in case you need a refresher on any of the steps.

Once again, a full code example can be found in our webiny-examples Github repository. And, if you have further questions, or you simply got stuck, feel free to get in touch with us via Slack.

FAQ#

How to Add Dependencies in Workspace Package?#

You can add dependencies to your workspace package using the yarn workspace <YOUR_WORKSPACE_NAME> add <DEPENDENCY_NAME> command.

For example, running the following command, will add the @webiny/form as dependency in the @extensions/pb-element-iframe workspace package:

yarn workspace @extensions/pb-element-iframe add @webiny/form
Last updated on by Adrian Smijulj