Create a New Page Element
Tutorial on how to create a new element for Webiny Page Builder.
- 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:
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.
By default, the extensions
folder does not exist in the apps
directory, 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:
- 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
- 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
- create a folder named
pb-element-iframe
insideapps/extensions
- create a
package.json
file insideapps/extensions/pb-element-iframe
with the following contents:
{
"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.26.0",
"@webiny/ui": "5.26.0",
"@webiny/validation": "5.26.0",
"classnames": "^2.2.6",
"emotion": "10.0.27",
"react": "^16.14.0"
}
}
Update the @webiny/*
dependencies versions above to match the version of your Webiny project.
You can check the Webiny version by running the yarn webiny --version
command from the project root.
- create a
tsconfig.json
file with the following contents:
{
"extends": "../../../tsconfig",
"include": ["."],
"compilerOptions": {
"composite": false
}
}
- add
"apps/extensions/pb-element-iframe"
toworkspaces
packages insidepackage.json
in project root
(...)"workspaces": {"packages": [ "apps/admin/code", "apps/website/code" // (...) "apps/extensions/pb-element-iframe"]// (...)}
create
admin
andrender
folders insideapps/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 innode_modules
add
@extensions/pb-element-iframe
package as a dependency to both the Admin Area application and Website application, by editing theapps/admin/package.json
andapps/website/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.
├── apps
│ ├── api
│ ├── 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:
As mentioned, this list of page elements can be expanded, and custom page elements can be created via plugins. Now let’s create all the necessary plugins for the Admin Area application.
Editor Element Plugin
We will start with thePbEditorPageElementPlugin
plugin.Create
index.tsx
file in apps/extensions/pb-element-iframe/admin
directory.import React from "react";
import styled from "@emotion/styled";
import {
PbEditorPageElementPlugin,
DisplayMode,
PbEditorPageElementAdvancedSettingsPlugin
} 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";
import IFrameSettings from "./iframeSettings";
const PreviewBox = styled("div")({
textAlign: "center",
height: 50,
svg: {
height: 50,
width: 50,
color: "var(--mdc-theme-text-secondary-on-background)"
}
});
export default () => {
return [
{
name: "pb-editor-page-element-iframe-custom",
type: "pb-editor-page-element",
elementType: "iframe-custom",
toolbar: {
// We use `pb-editor-element-group-basic` to put our plugin into the Media group.
title: "Custom iFrame",
group: "pb-editor-element-group-basic",
preview() {
return (
<PreviewBox>
<IFrameIcon />
</PreviewBox>
);
}
},
settings: [
"pb-editor-page-element-settings-delete",
"pb-editor-page-element-style-settings-height",
"pb-editor-page-element-style-settings-width"
],
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-custom",
elements: [],
data: {
iframe: {
// The URL property will be populated when user enters the URL in the element settings.
url: "",
height: 370
},
settings: {
height: createInitialPerDeviceSettingValue({ value: "380px" }, 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,
{
name: "pb-editor-page-element-advanced-settings-iframe-custom",
type: "pb-editor-page-element-advanced-settings",
elementType: "iframe-custom",
render(props) {
return <IFrameSettings {...props} />;
}
} as PbEditorPageElementAdvancedSettingsPlugin
];
};
Create an assets
folder in apps/extensions/pb-element-iframe/admin
then download and keep this iframe-icon.svg icon file in the assets
folder.
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.
Notice the props
that were passed to the render
function. This object contains all the relevant page element’s data and settings.
Now that we’ve covered the overall structure of the plugin. Let’s create an IFrameEditor
component.
It will be a simple React component that will render an iframe
HTML element.
Create components
directory in apps/extensions/pb-element-iframe/admin
and then create iframeEditor.tsx
file in components
directory.
import React from "react";
import { css } from "emotion";
import styled from "@emotion/styled";
import { ElementRoot } from "@webiny/app-page-builder/render/components/ElementRoot";
import { ReactComponent as IFrameIcon } from "../assets/iframe-icon.svg";
import { PbEditorElement } from "@webiny/app-page-builder/types";
const outerWrapper = css({
boxSizing: "border-box"
});
const PreviewBox = styled("div")({
textAlign: "center",
height: 50,
svg: {
height: 50,
width: 50,
color: "var(--mdc-theme-text-secondary-on-background)"
}
});
interface IFrameProps {
element: PbEditorElement;
}
const IframeEditor: React.FC<IFrameProps> = ({ element }) => {
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;
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.
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.
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 thePbEditorPageElementAdvancedSettingsPlugin
,
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.Create iframeSettings.tsx
file in the apps/extensions/pb-element-iframe/admin
directory.
import React from "react";
import { validation } from "@webiny/validation";
import { Input } from "@webiny/ui/Input";
import { Cell, Grid } from "@webiny/ui/Grid";
import {
ButtonContainer,
classes,
SimpleButton
} from "@webiny/app-page-builder/editor/plugins/elementSettings/components/StyledComponents";
import Accordion from "@webiny/app-page-builder/editor/plugins/elementSettings/components/Accordion";
import { BindComponent } from "@webiny/form";
interface iFrameImagesSettingsProps {
Bind: BindComponent;
submit: () => void;
}
const IFrameSettings: React.FC<iFrameImagesSettingsProps> = props => {
const { Bind, submit } = props;
return (
<Accordion title={"IFrame Source"} 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>
);
};
export default IFrameSettings;
As mentioned, this code will show the settings UI in sidebar and ask for the URL of the iframe
, as shown below:
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 thePbEditorPageElementPlugin
plugin.Create
index.tsx
file in the apps/extensions/pb-element-iframe/render
directory.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-custom",
render({ element }) {
return <IFrame element={element} />;
}
} as PbRenderElementPlugin);
Now let’s create the IFrame
component used in the index.tsx
above.
Create iFrame.tsx
file in the apps/extensions/pb-element-iframe/render/components
directory.
import React from "react";
import { css } from "emotion";
import { ElementRoot } from "@webiny/app-page-builder/render/components/ElementRoot";
import { PbEditorElement } from "@webiny/app-page-builder/types";
const outerWrapper = css({
boxSizing: "border-box"
});
interface IFrameProps {
element: PbEditorElement;
}
const IFrame: React.FC<IFrameProps> = ({ 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:
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/src/plugins/pageBuilder/editorPlugins.ts
file:
(...)
// Import the `iframe` element and it's settingsimport iframeElement from "@extensions/pb-element-iframe/admin";
export default [ iframeElement(), // Rest of the plugins (...)];
Also in apps/admin/src/plugins/pageBuilder/renderPlugins.ts
file:
(...)
// Import the `iframe` elementimport 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:
webiny watch
command up and running, the performed application code changes should be automatically rebuilt and redeployed into the cloud.Add Plugins in the Website Application
In the Website application, we will reuse the Render Element Plugin to render the iframe
page element.
To reuse the Render Element Plugin
, simply register it by making the following changes in the apps/website/src/plugins/pageBuilder.ts
file:
// Some code is removed for the sake of brevity.(...)
// Import `iframe` elementimport 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:
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