This article describes the new "Page editing" feature, which allows content editors to edit and preview page and block content directly.
Overview
The core of this feature involves displaying a website page within an iframe in the Litium backoffice. To enable live editing, an additional script is added to the website's frontend. This script provides two main functionalities:
-
Overlays: When hovering over specific blocks on the page (within the iframe), interactive overlays appear. These overlays contain buttons that trigger editing actions.
-
Hot Reloading: When a block is updated, the feature enables the page to "hot reload" the modified block, updating the preview without requiring a full page reload.
Scripts Integration
The scripts, named as Teleport, needs to be included in your website's frontend. They are automatically integrated into MVC and React Accelerators. For other frameworks, manual script inclusion and hot reload implementation are necessary. The following breakdown details the changes made to MVC and React Accelerators for reference.
Example (MVC):
The script is injected automatically by the system. Only the LiveEditing component needs to be added.
var editMode = (bool?)ViewContext.ViewData["EditMode"] == true;
@if (editMode)
{
<LiveEditing id="liveEditing" />
}
Example (React):
A preview query has been implemented in the React Storefront API to load preview-specific scripts and styles. This query utilizes the x-litium-storefront-context-url header to identify preview pages and avoid loading these resources on live pages. The preview data is queried in websiteService.server.ts, and Teleport files are included as shown below.
<body className="overflow-x-hidden">
{website.preview && (
<>
{website.preview.scripts.map((value, i) => (
<Script
key={'preview-script-' + i}
src={buildServerUrl(value.src)}
type={value.attributes?.find((a) => a.name === 'type')?.value}
strategy="beforeInteractive"
crossOrigin="anonymous
></Script>
))}
{website.preview.styleSheets.map((value, i) => (
<link
key={'preview-css-' + i}
href={buildServerUrl(value.src)}
rel="stylesheet"
></link>
))}
<LiveEditing>{children}</LiveEditing>
</>
)}
{!website.preview && children}
</body>
The <LiveEditing> component is crucial for hot reloading functionality.
Components
The script provides two main components:
-
CoverComponent:
- Scans the page for elements with a specific attribute (e.g.,
data-litium-block-id) that identify editable blocks.
- Creates an
OverlayComponent for each identified block.
-
OverlayComponent:
- Creates an overlay that appears on hover over a block.
- Contains buttons to trigger editing actions.
- Sends messages to the Litium backoffice when buttons are clicked.
Hot Reloading
The <LiveEditing> component handles hot reloading of updated blocks.
-
MVC: Listens for events from Litium and fetches updated block content from a specific controller (BlockPreviewController).
-
React: Listens for events and uses Next.js's revalidatePath function to update the page cache.
-
Other Frameworks: You'll need to implement a similar <LiveEditing> component to handle hot reloading based on your framework's capabilities. If no hot reloading is implemented, the iframe will be fully reloaded when a content is updated.
Communication Flow
Communication between the overlay, the <LiveEditing> component, and the Litium backoffice happens through events and messages:
-
User Interaction: The user clicks a button in the overlay (e.g., "Edit").
-
Overlay Action: The overlay captures the click and sends an event to the backoffice. The event includes the block ID.
-
Backoffice Processing: The backoffice receives the message, extracts the block ID, and performs the requested action (e.g., opens the block editor).
-
Hot Reload Request: If the backoffice updates a block, it sends an event back to the iframe to trigger a hot reload.
-
LiveEditing Response: The <LiveEditing> component in the iframe listens for these messages and updates the page content accordingly. Without it, or for unhandled events, the backoffice performs a full iframe reload. To listen for events that signal block updates, use the litiumBridge object. This object becomes available after importing the live editing script and provides a method called .on() to register event listeners. For example, to listen for the 'blockUpdated' event, you can use window.litiumBridge.on('blockUpdated', handleBlockUpdateEvent), where handleBlockUpdateEvent is your function to handle the update.
The following sequence diagram illustrates the block update process.

window.litiumBridge
This is a global object that is made available when you import the live editing script into your web page. It provides an interface for communication between the Accelerator website and the Backoffice.
| Name |
Parameters |
Description |
| on |
eventName: name of the event to listen
fn: callback function to be executed when the event is triggered
|
Registers an event listener for content change events. By registering an event, it informs the system that Accelerator can handle the event, enabling seamless updates within the iframe. Without registration, the system performs a full iframe reload for each update. |
Events
| Name |
Parameters |
Description |
| blockAdded |
{ systemId, referenceBlockSystemId, parentBlockSystemId, containerId, index addBefore, isContainerEmpty }
systemId: the system id of the new block.
referenceBlockSystemId: the system id of the existing block serves as a reference.
parentBlockSystemId: the system id of the parent block, if the adding block is a nested block.
containerId: the id of the container, if the adding block isn't a nested block.
index: the index to add block.
addBefore: a boolean value. If true, the new block is added before the reference block; if false, it is added after.
isContainerEmpty: a boolean value. If true, the container to add the block to doesn't have any block; if false, it has block(s).
|
When a block is added, or duplicated |
| blockUpdated |
{ systemId }
systemId: the system id of the block.
|
When a block is updated |
| blockDeleted |
{ systemId, parentBlockSystemId }
systemId: the system id of the block.
parentBlockSystemId: the system id of the parent block, if the deleting block is a nested block.
|
When a block is deleted |
| blockOrderChanged |
{ systemId, index, previousIndex, containerId, previousContainerId }
systemId: the system id of the block.
index: the new index.
previousIndex: the old index.
containerId: the id of the new container.
previousContainerId: the id of the old container.
|
When a block's order is changed |
| pageUpdated |
{ systemId }
systemId: the system id of the page.
|
When a page is updated |
Upgrade
To leverage the new Page editing feature after upgrading the Litium platform, you must update your existing Accelerator. The necessary file changes are outlined below for MVC and React Accelerators.
Ensure a smooth upgrade by consulting the upgrade and maintenance guidelines.
MVC Accelerator
- /Src/Litium.Accelerator.Mvc/Client/Scripts/Components/LiveEditing.js (Added)
- /Src/Litium.Accelerator.Mvc/Client/Scripts/index.js (Updated)
- /Src/Litium.Accelerator.Mvc/Controllers/Api/BlockPreviewController.cs (Added)
- /Src/Litium.Accelerator.Mvc/Controllers/ControllerBase.cs (Updated)
- /Src/Litium.Accelerator.Mvc/Extensions/BlockContainerHelperExtension.cs (Updated)
- /Src/Litium.Accelerator.Mvc/Views/Shared/_Layout.cshtml (Updated)
React Accelerator
- /app/actions/liveEditing/revalidatePage.ts (Added)
- /app/layout.tsx (Updated)
- /components/blocks/LiveEditing.tsx (Added)
- /models/preview.ts (Added)
- /models/website.ts (Updated)
- /services/imageService.ts (Updated)
- /services/urlService.ts (Updated)
- /services/websiteService.server.ts (Updated)
Known issues
- Using the same global block multiple times within a page may cause updates to be applied to the wrong global block.
- To add blocks to an empty page, the classic Page editor should be used instead.