dotCMS Page Rendering Architecture
1. Introduction#
This document outlines the architecture for rendering dotCMS pages with editing capabilities. It is intended for developers of all skill levels who are building web applications that integrate with dotCMS.
The document covers the core components of the architecture, the communication protocols involved, and the processes for rendering and updating page content. It also provides guidance on implementation considerations, best practices, and troubleshooting.
This document uses code examples in various languages, including JavaScript, HTML, and CSS, to illustrate concepts. These will be presented in code blocks.
1.1 Key Concepts#
This section provides a deeper dive into the key concepts that underpin the dotCMS page rendering architecture. Understanding these concepts is essential for successfully implementing and customizing your dotCMS-powered web applications.
1.1.1 Page Assets#
A page asset is a comprehensive collection of data that represents a complete web page in dotCMS. It encapsulates everything necessary to render the page, including its layout, content, and associated properties.
Key Components of a Page Asset:
- Layout: The structural framework of a page, defining how content is arranged. A layout in dotCMS consists of rows and columns based on a 12-column grid system. It determines the placement of containers, which in turn hold contentlets.
- Template: A predefined structure that provides consistency across multiple pages. Templates include layouts, default content placements, and styling guidelines. In dotCMS, templates define how content is arranged but are flexible enough to accommodate dynamic content changes.
- Page: A content asset in dotCMS that represents a complete webpage. Pages store layout, contentlets, metadata, and properties such as SEO information, URL, and navigation settings. Pages can be rendered using dotCMS’s built-in layout engine or as headless content through APIs.
- Row: A horizontal section within a layout that helps organize content. Rows are part of the 12-column grid system, ensuring a structured layout that adapts to different screen sizes.
- Column: A vertical unit within a row, defining the width allocation of content within the 12-column structure. Columns control how content adapts to different devices by adjusting width and positioning.
- Container: A placeholder within a layout that holds contentlets. Containers define:
- Placement of content within the page.
- Accepted content types (restricting what contentlets can be added).
- Ordering rules for dynamic content arrangement.
- Contentlet: An individual content item within dotCMS, created based on a Content Type schema. Examples include blog posts, product descriptions, and event details. Contentlets can be placed within containers and are managed independently from page layouts.
- Site: A website or section within dotCMS that organizes related pages, templates, and assets under a common structure. A dotCMS instance can manage multiple sites, each with unique branding, content rules, and permissions.
- Vanity URL: A custom-friendly URL that redirects to a specific page. Vanity URLs improve SEO and usability by providing shorter, more memorable links. In headless implementations, the dotCMS Page API supports routing and resolving these URLs dynamically.
- ViewAs: A rendering context in dotCMS that allows users to preview a page as a specific persona, language, or personalization variant. The ViewAs settings influence what content is shown based on visitor attributes like location, device type, or audience segmentation.
- NavItem: A navigation element representing a page or link within a site’s menu structure. NavItems can be dynamically generated based on a site’s content hierarchy and can be customized using the Navigation API.
Example: Accessing Page Title
const pageTitle = pageAsset.page.title;
1.1.2 Contentlets#
Contentlets are the individual units of content managed within dotCMS. They can be of various types, each adhering to a specific content type schema.
For the record, pages are also contentlets and can have user-defined fields too.
Key Characteristics of a Contentlet:
- Content Type: Defines the structure (schema) and properties that a contentlet of a particular type can possess.
- Base Properties: Common properties shared by all contentlets, such as a unique identifier, inode (version identifier), creation date, and some others.
- User-defined fields: fields specific to each content type, allowing content editors to create and manage diverse content.
Example: Accessing Contentlet Title
const contentletTitle = contentlet.title;
1.1.3 Content Types#
Content types are the schemas that define the structure and properties of contentlets in dotCMS. They determine what kind of information a contentlet can hold and how it is organized.
Key Features of Content Types:
- Custom Fields: Allow defining specific fields relevant to the content type, such as "Title," "Body," "Image," or any other custom property.
- Field Types: Offer a variety of field types (e.g., text, number, date, image, video) to capture different kinds of information.
- Relationships: Enable creating relationships between content types, allowing contentlets to be linked or associated with each other.
Example: Defining a "Blog Post" Content Type
{ "name": "Blog Post", "fields": [ { "name": "Title", "type": "text" }, { "name": "Body", "type": "textarea" }, { "name": "Author", "type": "text" } ] }
1.1.4 Containers#
Containers are designated areas within a page layout that hold and manage contentlets. They act as the bridge between the layout structure and the dynamic content displayed on the page.
Key Functions of Containers:
- Content Placement: Determine where contentlets are positioned within the layout.
- Content Type Restrictions: Define which content types are allowed to be placed within a specific container.
- Layout-Content Relationship: Manage the relationship between the layout structure and the dynamic content, ensuring that content is displayed in the correct location and format.
Example: A container in a layout
<div data-dot-object="container" data-dot-accept-types="Blog"></div>
1.1.5 Layout Engine#
Is the core component responsible for interpreting the layout structure defined in dotCMS and rendering it into a visual layout on the web page. It ensures that the content is organized and displayed according to the design specified in the page asset.
Key Responsibilities of the Layout Engine:
- Grid System Management: Handles the organization of content into rows and columns, typically using a 12-column grid system.
- Component Positioning: Determines the placement of containers and contentlets within the layout grid, ensuring proper alignment and spacing.
1.1.6 Universal Visual Editor (UVE)#
The Universal Visual Editor (UVE) is dotCMS's content editing interface. It provides a user-friendly, drag-and-drop environment for content editors to create, modify, and manage the layout, content and properties of a web pages.
Key Features of the UVE:
- Inline Editing: Allows content editors to make changes to content directly on the page without having to navigate to separate editing forms.
- Drag-and-Drop Functionality: Enables content editors to easily move and rearrange contentlets within containers to create the desired layout.
- Content Preview: Provides a real-time preview of how the page will look with the applied changes, allowing content editors to visualize the final result before publishing.
- Navigation reorder: Provide inline controls to organize the website navigation when using the navigation API.
1.2 Architecture Goals#
- Framework Independence: It is designed to be implemented across various web frameworks while maintaining consistent behavior and functionality.
- Seamless Editing: It provides a natural, in-context editing experience through tight integration with the Universal Visual Editor.
- Performance: It supports efficient rendering and updates while maintaining a responsive user experience.
- Scalability: It can accommodate growing content needs and complex page structures.
- Maintainability: It promotes clean separation of concerns and modular design.
1.3 Prerequisites#
Before implementing this architecture, ensure you have:
- A basic understanding of dotCMS core concepts like Pages, Layouts, and Content Types.
- Access to a dotCMS instance with appropriate credentials.
- Familiarity with the dotCMS REST API
- Knowledge of your chosen framework
2. Core Architecture Components#
The dotCMS Page Rendering Architecture transforms raw content from a dotCMS instance into interactive web pages. It does this through two main processes:
- Layout processing: This process interprets the page structure defined in dotCMS and translates it into a visual layout. Think of this as building the scaffolding of the page.
- Content rendering: This process involves fetching the actual content from dotCMS and displaying it within the layout. This is like filling in the scaffolding with the actual content.
These two processes work in tandem to deliver the final webpage that users see and interact with.
(Consider adding a diagram here illustrating the relationship between the layout engine and container management)
2.1 Layout Processing#
The Layout Engine is the core of this process. Its job is to read the page structure defined in dotCMS (which includes elements like rows, columns, and containers) and convert it into a visual layout. The Layout Engine also manages the grid system, which is a 12-column structure that dotCMS uses to arrange content on the page.
Responsive Design#
The Layout Engine is designed to handle responsive design, meaning it ensures the page looks good on all devices (desktops, tablets, and phones). It does this by adjusting the layout based on the screen size.
How it adapts to different screen sizes#
The grid system is based on a 12-column layout. The width
property of a column determines how many columns it spans out of the 12. The leftOffset
property, when used in conjunction with width
, determines the starting position and span of a column within the 12-column grid.
Example:
-
If a column has
width: 6
, it will take up half of the row, regardless of the screen size. If theleftOffset
is 3, it will start at the third column and span 6 columns. -
If a column has
width: 12
, it will span the entire width of the row.
CSS Code Example#
Here's an example of how you might use CSS to create a responsive layout with two columns:
.container { display: grid; grid-template-columns: repeat(12, 1fr); /* Creates a 12-column grid */ gap: 1rem; /* Adds spacing between columns */ } .column { /* ... other column styles ... */ } /* Styles for smaller screens (e.g., mobile) */ @media (max-width: 768px) { .column { grid-column: span 12; /* Columns stack vertically */ } }
2.2 Content Rendering#
Content rendering is the process of taking the actual content from a dotCMS page and displaying it within the layout structure. This involves a few key steps:
- Getting Content: Retrieve the contentlet from the page asset object.
- Content Type → Component Mapping: Map the content types of the retrieved contentlets to their corresponding UI components. This ensures that each type of content is displayed using the appropriate component.
- Dynamic Rendering: Use the mapped components to dynamically render the content on the page.
2.2.1 Fallback Components for Unknown Content Types#
In some cases, you might encounter a content type for which you haven't created a specific component. To gracefully handle this, you can configure a fallback component that will be used when a content type doesn't have a corresponding component in your mapping.
Implementation
- Create a Fallback Component: This component should be designed to handle any content type and display the content in a generic but informative way. For example, it might simply display the content title and a message indicating that a dedicated component is not yet available.
- Configure the Fallback Component: In your content type mapping, specify this fallback component as the default component to be used when a content type is not found.
2.2.2 Performance Considerations: Lazy Loading (Optional)#
While not a requirement, framework-specific features like lazy loading can be valuable for optimizing content rendering, especially for pages with a large amount of content. Lazy loading allows you to defer the loading of content until it's actually needed, which can significantly improve initial page load times. This is particularly useful in client-side rendering scenarios.
If you are using server-side rendering, lazy loading might not be necessary or beneficial, as the content is typically rendered upfront on the server.
Implementation
Here are some examples of how you might implement lazy loading in different frameworks:
React
import React, { lazy, Suspense } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> ); }
Angular
import { Component } from '@angular/core'; @Component({ selector: 'app-my-component', template: ` <div *ngIf="myComponent"> </div> ` }) export class MyComponent { myComponent = import('./my.component').then((m) => m.MyComponent); }
2.3 Container Management#
Containers are key to organizing content within a dotCMS page. They act as placeholders in the layout, defining both what content can appear in a given area and how it should be displayed.
Defining Content Placement#
Containers work within the grid system described earlier. Each container is assigned to a specific column in a row. This determines its position on the page. The order of containers within a column also matters – they are rendered from top to bottom as they appear in the layout definition.
Specifying Acceptable Content Types#
One of the primary functions of a container is to restrict the types of content that can be placed within it. This is done using the data-dot-accept-types
attribute. This attribute accepts a comma-separated list of Content Type variable names (e.g., "BlogPost, NewsArticle, Product"). This ensures that only content of the allowed types can be dragged into the container in the editor.
Linking Layout and Content#
Containers act as the bridge between the layout and the actual content. They do this by:
- Referencing Content: Each container in the layout references the actual content it should hold using a unique identifier (
data-dot-identifier
). This identifier points to a specific contentlet or a collection of contentlets stored in dotCMS. - Defining Display Rules: Containers can also include additional information that affects how the content is displayed. This might include things like the maximum number of contentlets allowed in the container (
data-max-contentlets
) or the order in which they should be displayed.
Example Container Wrapping div#
<div data-dot-object="container" data-dot-accept-types="Blog, News" data-dot-identifier="123-ABC" data-max-contentlets="5"> </div>
In this example, the container:
- Is assigned to a specific position in the layout based on its placement within a row and column.
- Only allows content of the types "Blog" or "News" to be placed within it.
- Will display content from the container identifier as "123-ABC" in dotCMS.
- Will not display more than 5 contentlets.
2.4 Routing#
The Routing component is a crucial part of this architecture. It acts as the single entry point for all dotCMS pages in your web application. This means that instead of defining separate routes for each individual page in your application, you have one catch-all route that handles all dotCMS page requests.
With this approach content authors can create any page and folder in the system without developer intervention.
How it Works#
- Catch-all Route: You set up a catch-all route in your application. This route will handle any URL that is not explicitly defined as a route within your application.
- dotCMS Routing: When a user requests a page, the Routing component intercepts the request and extracts the relevant URL information. It then uses this information to query the dotCMS Page API.
- Page API Interaction: The Page API uses the provided URL to locate the corresponding dotCMS page. It then retrieves the page asset for that page, which includes the layout, content, and other page properties.
- Dynamic Rendering: The Routing component then passes the retrieved page asset to the Layout Engine and Content Rendering components, which dynamically render the page based on the information in the page asset.
Benefits of Single Entry Point Routing#
- Decoupling: Decouples your frontend routes from the dotCMS structure, giving content editors more freedom to create and manage pages without affecting your application's routing configuration.
- Dynamic Content: Enables truly dynamic page creation, where the content and structure of a page are determined at runtime based on the information fetched from dotCMS.
- Reduced Maintenance: Reduces maintenance overhead by eliminating the need to create and manage individual routes for each page in your application.
- Supports CMS-Managed Rewrites: the page APIs support the dotCMS concept of vanity Urls which allow for rewrites/redirects to be managed by users in the CMS
Code Examples#
Here are some examples of how you might implement a single-entry point routing component in different frameworks:
React (using React Router)
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { DotCMSPage } from './components/DotCMSPage'; function App() { return ( <Router> <Routes> <Route path="/*" element={<DotCMSPage />} /> </Routes> </Router> ); }
Angular
import { Routes, Route } from '@angular/router'; import { DotCMSPageComponent } from './dotcms-page.component'; const routes: Routes = [ { path: '**', component: DotCMSPageComponent } ];
Next.js
// In your `pages/[...slug].js` file: import { DotCMSPage } from './components/DotCMSPage'; export default function Page() { return <DotCMSPage />; }
2.5 Editor Integration#
Editor Integration is the process that enables real-time content editing through communication between your page and dotCMS's Universal Visual Editor (UVE).
2.5.1 Communication Protocol#
The page and the editor communicate through the postMessage
API. This API allows windows (like your webpage and the dotCMS editor) to send messages to each other. The architecture defines a set of standardized actions that are sent as part of these messages to ensure that both the page and the editor understand what each message means.
2.5.2 Event System#
The communication between the page and the editor is event-driven. This means that the page and the editor 'listen' for specific events from each other and react accordingly. This allows for real-time updates and ensures that both the page and the editor are always in sync.
2.5.3 Message Types#
Here are some examples of the types of messages that are exchanged between the page and the editor:
Editor to Page
-
Content Updates: These messages notify the page when content has changed in the editor (e.g., a contentlet was added, removed, or edited). The page can then update its content to reflect the changes.
-
State Synchronization Messages: These messages inform the page about the editor's current state (e.g., the current language, the persona, the URL). This ensures that the page is always showing the correct content for the context in which it's being edited.
Page to Editor
-
Inline Editing Events: These messages request the editor to enable inline editing for a specific contentlet. The editor responds with the necessary information for the page to initialize the inline editing interface.
-
Content Hover Messages: When a user hovers over a contentlet, the page sends a message to the editor to identify the hovered contentlet and its position. This allows the editor to display the appropriate editing tools and highlight the contentlet for editing.
3. Implementation Guide#
This section provides a step-by-step guide to implementing the dotCMS page rendering architecture in your web application. It covers the core setup, layout implementation, content rendering, and editor integration.
3.1 Core Setup#
Before you start implementing the architecture, you need to make sure you have the following:
- dotCMS Instance URL: The URL of your dotCMS instance (e.g.,
https://your-dotcms-instance.com
). - Authentication Token: An authentication token with the necessary permissions to access the dotCMS REST APIs. You can generate an authentication token in your dotCMS instance by going to Settings > Users > API Access Tokens.
- Note: It's crucial to store your authentication token securely. Avoid hardcoding it directly in your client-side code. Instead, consider using environment variables or a secure configuration service.
- Site ID (Optional): The ID of the specific site you want to work with in dotCMS. If you don't provide a site ID, the default site will be used.
3.1.1 API Integration#
You'll primarily be interacting with two core dotCMS REST APIs:
- Page API: This API is used to fetch the page layout, containers, and content. The format for this API is:
/api/v1/page/json/{path}
. You can also pass parameters likelanguage
andmode
to this API. For detailed information and examples, refer to the Page API documentation. - Navigation API: This API is used to retrieve the site's navigation structure. The format for this API is:
/api/v1/nav/{path}
. You can pass parameters likedepth
andlanguageId
to this API. For detailed information and examples, refer to the Navigation API documentation.
3.1.2 Single Entry Point Architecture#
A key concept in this architecture is using a single entry point to handle all dotCMS pages. This means that instead of creating separate routes for each page in your web application, you use a catch-all route and let dotCMS handle the page resolution and routing.
Benefits of this approach:
- Decouples your frontend routes from the CMS structure.
- Enables dynamic page creation and management by content editors.
- Reduces maintenance overhead by not having to create and manage individual routes.
3.1.3 Editor Communication#
When your page is rendered within the dotCMS editor, it needs to be able to communicate with the editor to support content editing and real-time updates.
- Detect if Running Inside Editor: Your page should be able to detect if it's running within the dotCMS editor. You can do this by checking if
window.parent !== window
. - Setup
postMessage
Communication: Use thepostMessage
API to send and receive messages between the page and the editor. - Initialize Edit Mode: When the page detects that it's running inside the editor, it should initialize the edit mode and set up the necessary event listeners to handle editor messages.
Example (JavaScript):
if (window.parent !== window) { // Running inside the editor window.addEventListener('message', (event) => { // Handle editor messages here }); }
3.2 Layout Implementation#
This section dives into how to build the structure for your pages using the layout information from dotCMS.
Understanding the Grid System#
dotCMS employs a 12-column grid system for its layouts. This is a standard approach in web design, providing flexibility and responsiveness. Here's how it works:
- Columns: The
width
property of a column in the layout data determines how many columns out of the 12 it will occupy. - Offsets: The
leftOffset
property shifts the column to the right by the specified number of columns. This creates horizontal space between elements.
Example:
A column with width: 6
and leftOffset: 3
starts at the 4th column line and extends for 6 columns.
Calculating Positions#
While dotCMS provides width
and leftOffset
, your implementation needs to translate these into the actual position of columns within the grid.
Steps:
- Start Position: The
leftOffset
directly translates to the starting grid line of the column. - End Position: Calculate the end position by adding the
width
to theleftOffset
. - Apply to Grid: Use your framework's grid system or CSS to place the column, ensuring it starts and ends at the calculated positions.
Handling Responsive Breakpoints#
dotCMS provides a single layout for each page. To make your pages responsive (adapt to different screen sizes), you'll leverage CSS grids and media queries.
Example:
/* Standard layout for larger screens */ .column { grid-column-start: var(--column-start); grid-column-end: var(--column-end); } /* Adjust for smaller screens */ @media (max-width: 768px) { .column { /* Stack columns vertically */ grid-column-start: 1; grid-column-end: 13; } }
Implementing Layout Components#
You'll need to create components that represent the elements of a dotCMS layout:
1. Row Component
- Represents a row in the layout.
- Contains and positions Column components.
- Applies any styling specified in the
styleClass
property of the row data.
2. Column Component
- Represents a column within a row.
- Calculates and applies its position in the grid using
leftOffset
andwidth
. - Contains Container components.
- Applies any styling specified in the
styleClass
property of the column data.
3. Container Component
- Represents a container that holds contentlets.
- Enforces rules about what content types can be placed inside.
- Renders the contentlets it contains.
Framework-Specific Examples
While the core concepts of layout implementation remain consistent, the specific code for rendering rows, columns, and containers will vary depending on your chosen framework. Below are examples in React, Angular, and .NET to illustrate how you might adapt the general principles to your specific technology.
React
// Row Component const Row = ({ row }) => ( <div className={`row ${row.styleClass}`}> {row.columns.map((column) => ( <Column key={column.identifier} column={column} /> ))} </div> ); // Column Component const Column = ({ column }) => { const start = column.leftOffset; const end = start + column.width; return ( <div className={`column ${column.styleClass}`} style={{ gridColumn: `${start} / ${end}` }} > {column.containers.map((container) => ( <Container key={container.identifier} container={container} /> ))} </div> ); }; // Container Component const Container = ({ container }) => ( <div data-dot-object="container" data-dot-accept-types={container.acceptTypes} data-dot-identifier={container.identifier} // ... other container attributes ... > {/* Render contentlets here */} </div> );
Angular
// Angular // Row Component @Component({ selector: 'app-row', template: ` <div class="row" [ngClass]="row.styleClass"> <app-column *ngFor="let column of row.columns" [column]="column"></app-column> </div> ` }) export class RowComponent { @Input() row: any; } // Column Component @Component({ selector: 'app-column', template: ` <div class="column" [ngClass]="column.styleClass" [style.grid-column]="gridColumn"> <app-container *ngFor="let container of column.containers" [container]="container"></app-container> </div> ` }) export class ColumnComponent { @Input() column: any; get gridColumn(): string { const start = this.column.leftOffset; const end = start + this.column.width; return `${start} / ${end}`; } } // Container Component @Component({ selector: 'app-container', template: ` <div data-dot-object="container" [attr.data-dot-accept-types]="container.acceptTypes" [attr.data-dot-identifier]="container.identifier" // ... other container attributes ... > </div> ` }) export class ContainerComponent { @Input() container: any; }
.NET
// .NET (using ASP.NET Core Razor Pages) // Row.cshtml @model RowViewModel <div class="row @Model.StyleClass"> @foreach (var column in Model.Columns) { <partial name="_Column" model="column" /> } </div> // _Column.cshtml @model ColumnViewModel @{ var start = Model.LeftOffset; var end = start + Model.Width; } <div class="column @Model.StyleClass" style="grid-column-start: @start; grid-column-end: @end;" > @foreach (var container in Model.Containers) { <partial name="_Container" model="container" /> } </div> // _Container.cshtml @model ContainerViewModel <div data-dot-object="container" data-dot-accept-types="@Model.AcceptTypes" data-dot-identifier="@Model.Identifier" // ... other container attributes ... > @* Render contentlets here *@ </div>
Model Classes
You'll need corresponding C# classes to represent the row, column, and container data:
public class RowViewModel { public string StyleClass { get; set; } public List<ColumnViewModel> Columns { get; set; } } public class ColumnViewModel { public int LeftOffset { get; set; } public int Width { get; set; } public string StyleClass { get; set; } public List<ContainerViewModel> Containers { get; set; } } public class ContainerViewModel { public string AcceptTypes { get; set; } public string Identifier { get; set; } // ... other container properties ... }
Important Considerations:
- Data Fetching: These examples assume you have already fetched the layout data from the Page API and are passing it as props/inputs to the components.
- Content Rendering: The
Container
component in each example would need to handle fetching (from the local state) and rendering the actual contentlets based on thecontainer.identifier
and content type mapping. - Error Handling: Implement robust error handling to gracefully manage scenarios where layout data is missing or invalid.
- Editor Integration: Add the necessary
data-dot
attributes and event listeners to support interaction with the dotCMS editor, as described in other parts of the document. - Performance: For complex layouts or a large number of contentlets, consider performance optimization techniques like lazy loading or virtualization.
- Accessibility: Ensure your layout implementation adheres to accessibility best practices to make your pages usable by everyone.
Remember to adapt these examples to your specific needs and framework conventions. The goal is to demonstrate the core principles of layout implementation within the dotCMS page rendering architecture.
3.3 Content Rendering#
Content rendering involves getting the content from the page asset and dynamically displaying it within the defined layout. This process includes:
- Fetching Content: Retrieving contentlets and their associated data from the page asset.
- Content Mapping: Mapping content types to their corresponding UI components ("Content Type Component Mapping").
- Dynamic Rendering: Using the mapped components to display the content in the appropriate format.
3.3.1 Setting up a Component Registry#
A component registry is a mechanism that maps content types to their corresponding UI components. This allows you to dynamically render content based on its type without having to hardcode the rendering logic for each content type.
Requirements:
- Map Content Types to Components: The registry should be able to map each content type to a specific component.
- Fallback Component: It should provide a default or fallback component for unknown content types.
- Resolution: The registry should be able to resolve the component for a content type at runtime (server or client) or a build time if you are statically building your pages.
Structure:
- Clear Mapping: The registry should have a clear and well-defined mapping between content types and components.
- Maintainability: The structure should be easy to maintain and scale as you add more content types and components.
- Extensibility: It should be easy to add new content type mappings to the registry.
Example (JavaScript):
const componentRegistry = { 'Blog Post': BlogPostComponent, 'News Article': NewsArticleComponent, 'Product': ProductComponent, 'default': DefaultComponent // Fallback component };
3.3.2 Content Type Mapping#
When rendering contentlets, your implementation should be able to:
- Resolve Content Type: Identify the content type of the contentlet.
- Locate Component: Find the corresponding component for the content type in the registry.
- Render Component: Render the component with the contentlet data.
- Handle Unknown Types: Use the fallback component if the content type is not found in the registry.
3.3.3 Dynamic Rendering#
Dynamic rendering involves using the mapped components to display the content in the appropriate format. This allows you to render different types of content on the same page without having to write separate rendering logic for each type.
3.3.4 Handling Content Updates#
In edit mode, your implementation needs to be able to handle content updates from the editor. This includes:
- Editor Integration: Adding the necessary attributes to contentlet wrappers to enable editor interaction.
- Update Management: Processing content changes, updating specific contentlets, maintaining editor state, and handling error cases.
- Content Reloading: Supporting content reloading when needed to reflect changes made in the editor.
Example (JavaScript):
// Assuming you have a contentlet object const contentlet = { contentType: 'Blog Post', // ... other contentlet data }; // Resolve the component const Component = componentRegistry[contentlet.contentType] || componentRegistry['default']; // Render the component return <Component {...contentlet} />;
You're absolutely right! The payloads are essential to understanding how the page and editor communicate. Here's the revised section with detailed payload information:
3.4 Event Handling and Communication#
This section explains the events passed between the dotCMS Page Editor and your implemented page, how to handle incoming events from the editor, and how to trigger outgoing events to the editor.
Events Received from the Editor#
-
Content Updates (
NOTIFY_CLIENT.UVE_SET_PAGE_DATA
)- Payload: The updated page asset object. This includes all the latest information about the page, such as its layout, containers, contentlets, and page properties.
- Implementation:
- Update the page's content with the new data received in the payload. This might involve replacing existing contentlets, adding new ones, or updating the layout.
- Re-render the affected components to reflect the changes. This ensures that the page displays the latest content from the editor.
-
Scroll Requests (
NOTIFY_CLIENT.UVE_SCROLL_INSIDE_IFRAME
)- Payload:
{ direction: 'up' | 'down' }
This indicates the direction in which the page should be scrolled. - Implementation:
- Scroll the page in the requested direction. You can use the browser's built-in scrolling functions or a library to handle the scrolling behavior.
- Consider using smooth scrolling for a better user experience. This can make the scrolling animation more visually appealing.
- Payload:
Events Sent from the Page#
-
Navigation Updates (
CLIENT_ACTIONS.NAVIGATION_UPDATE
)- Payload:
{ url: '/the-new-page' }
This contains the URL of the page the user has navigated to. - Source Component: Routing Component
- Trigger: When the user navigates to a different page within your application. This could be triggered by clicking a link, submitting a form, or using the browser's navigation buttons.
- Implementation: Send this event to update the navigation state in the editor. This ensures that the editor's navigation reflects the user's current location within your application.
- Payload:
-
Element Bounds (
CLIENT_ACTIONS.SET_BOUNDS
)- Payload:
[ { "x": 100, "y": 200, "width": 300, "height": 150, "payload": { "container": { "identifier": "container-1", "uuid": "12345678-1234-1234-1234-1234567890ab" // ... other container properties ... } }, "contentlets": [ { "x": 10, "y": 20, "width": 50, "height": 30, "payload": { "contentlet": { "identifier": "contentlet-1", "inode": "abcdefgh-abcd-abcd-abcd-abcdefghijkl" // ... other contentlet properties ... } } } // ... more contentlets ... ] } // ... more containers ... ]
This payload is an array of container objects. Each container object includes its position (`x`, `y`), dimensions (`width`, `height`), and a `payload` containing the container identifier and UUID. It also includes an array of `contentlets` within that container, each with their positions relative to the container and a `payload` with the contentlet identifier and inode.
-
Source Component: Layout Engine
-
Trigger: After the layout is rendered or updated. This event is typically sent once the page has finished rendering or whenever there's a change in the layout that affects the position or size of containers or contentlets.
-
Implementation:
- Calculate the bounds (position and size) of each container and contentlet on the page using the DOM API (e.g.,
getBoundingClientRect()
). - Send this event to the editor to provide the position and size information of the elements. The editor uses this information to correctly display overlays, drag-and-drop areas, and other editing tools.
- Calculate the bounds (position and size) of each container and contentlet on the page using the DOM API (e.g.,
-
Content Hover (
CLIENT_ACTIONS.SET_CONTENTLET
)- Payload:
{ "x": 150, "y": 250, "width": 200, "height": 100, "payload": { "identifier": "contentlet-1", "inode": "abcdefgh-abcd-abcd-abcd-abcdefghijkl", "contentType": "Blog Post", // ... other contentlet properties ... } }
This payload includes the position (`x`, `y`), dimensions (`width`, `height`), and a `payload` with detailed information about the contentlet being hovered over, including its identifier, inode, and content type.
-
Source Component: Container Component
-
Trigger: When the user hovers the mouse over a contentlet. This allows the editor to highlight the contentlet and display relevant editing options.
-
Implementation: Send this event to the editor to identify the hovered contentlet and its position. The editor uses this information to provide visual feedback to the user and enable editing actions.
-
Edit Contentlet Request (
CLIENT_ACTIONS.EDIT_CONTENTLET
)- Payload:
{ "identifier": "contentlet-1", "inode": "abcdefgh-abcd-abcd-abcd-abcdefghijkl", "contentType": "Blog Post", // ... other contentlet properties ... }
This payload contains the identifier, inode, content type, and other relevant properties of the contentlet to be edited.
-
Source Component: Typically a button or link within a contentlet's display.
-
Trigger: When the user clicks an "Edit" button or link for a contentlet. This indicates the user's intent to edit the contentlet.
-
Implementation: Send this event to the editor to open the contentlet editing interface. The editor will use the provided information to load and display the contentlet for editing.
-
Reorder Menu (
CLIENT_ACTIONS.REORDER_MENU
)- Payload:
{ startLevel: 1, depth: 2 }
startLevel
indicates the level of the page in the sitemap where the reorder action originated.depth
specifies how many levels deep the reorder should affect. - Source Component: A button or UI element specifically designed for triggering menu reordering.
- Trigger: When the user interacts with the "Reorder Menu" element, indicating their intent to reorder the navigation menu.
- Implementation: Send this event to the editor to activate the menu reordering functionality. The editor will respond by enabling the user to drag and drop pages to reorder them in the navigation.
- Payload:
-
Get Page Data (
CLIENT_ACTIONS.GET_PAGE_DATA
)- Payload:
{ pathname: '/the-page-path' }
This contains the pathname of the current page. - Source Component: The main page component or layout component.
- Trigger: When the page is initially loaded within the editor. This is typically one of the first events sent to the editor.
- Implementation: Send this event to request the page data from the editor. The editor will respond with the page asset, which contains the layout and content information needed to render the page.
- Payload:
-
Client Ready (
CLIENT_ACTIONS.CLIENT_READY
)- Payload:
{ "params": { "depth": "2", // Depth of related content to fetch "query": "{ ... }" // Optional GraphQL query } }
4. Best Practices and Guidelines#
Architecture Patterns#
- Separation of Concerns
- Keep layout processing separate from content rendering: Layout logic should handle structure while content components focus on their specific rendering responsibilities.
- Isolate editor communication logic: Create dedicated services or utilities for editor interactions to maintain clean communication channels and easier debugging.
- Create clear boundaries between dotCMS integration and application code: Your application should work independently of dotCMS, with integration points clearly defined and isolated.
- Component Design
- Build reusable components: Create components that can be used across different content types and layouts with consistent interfaces.
- Keep content components independent: Each component should render its content without dependencies on other components or global state.
- Maintain clear component interfaces: Define explicit props and data requirements for each component to ensure proper content rendering.
- Document component responsibilities: Clearly specify what each component does, its requirements, and how it integrates with the page structure.
- State Management
- Keep page state independent of editor state: Ensure your page works properly whether in edit mode or not.
- Handle content updates efficiently: Implement update mechanisms that don't require full page reloads or disrupt user experience.
- Maintain clean data flow: Establish clear patterns for how content and layout data moves through your application.
Error Handling#
- API Errors
- Handle 404 for non-existent pages: Provide user-friendly messages and navigation options when pages aren't found.
- Process authentication errors: Implement proper handling for 401 and 403 responses with clear user feedback.
- Manage API timeouts: Set appropriate timeout limits and provide feedback when requests take too long.
- Provide fallback content: Display alternative content when primary content fails to load while maintaining page functionality.
Security Considerations#
-
Authentication
- Secure API token storage: Never expose authentication tokens in client-side code or store them in insecure locations.
- Handle token expiration: Implement refresh mechanisms and graceful session handling when tokens expire during editing.
- Implement proper authorization checks: Verify user permissions before allowing edit operations or accessing restricted content.
-
Content Security
- Sanitize rendered content: Clean and validate all content before rendering to prevent XSS attacks or malicious code execution.
- Validate content types: Ensure content matches expected types and structures before rendering to prevent injection attacks.
- Handle user permissions: Check content access permissions and respect dotCMS's content restrictions.
- Protect editor communications: Validate all messages between editor and page to prevent unauthorized modifications.
-
URL Management
- Validate URL parameters: Check all URL inputs for validity and sanitize parameters before use.
- Handle cross-origin requests: Implement proper CORS policies and validate request origins.
- Protect against injection attacks: Sanitize URL inputs and validate redirects to prevent malicious redirections.
Performance Tips#
- Content Loading
- Implement efficient content resolution: Optimize how content is fetched and processed to minimize load times.
- Optimize component updates: Update only necessary components when content changes instead of full page rerenders.
- Consider caching strategies: Cache resolved content and component mappings to improve subsequent page loads.
- Minimize unnecessary rerenders: Implement proper component update checks to prevent unnecessary rerendering.
- Resource Management
- Clean up unused components: Remove event listeners and clear resources when components are unmounted.
- Handle memory efficiently: Monitor and manage memory usage, especially with dynamic content loading.
- Manage event listeners properly: Add and remove event listeners appropriately to prevent memory leaks.
- Optimize editor communication: Minimize message passing between page and editor to reduce overhead.
- Page Optimization
- Efficient layout processing: Optimize layout calculations and grid system implementation for faster rendering.
- Optimize content updates: Implement efficient update mechanisms that minimize page recalculations.
- Handle large page structures: Implement virtualization or pagination for pages with many components.
- Implement proper cleanup: Ensure resources are released when navigating between pages.
- Use depth parameter sparingly as it can unwittingly result in huge page payloads. For deeply nested page queries, we suggest using the GraphQL page endpoint and bespoke queries.
5. Troubleshooting and Support#
Common Issues#
- Editor Integration Problems
- Editor not detecting page: Verify proper page loading inside editor iframe and postMessage communication setup.
- Content updates not reflecting: Check event listeners and message handling between editor and page.
- Edit mode not initializing: Ensure proper editor detection and initialization sequence.
- Layout Issues
- Grid misalignment: Verify column width and offset calculations match dotCMS grid system.
- Container rendering problems: Check container references and content resolution process.
- Responsive layout breaks: Validate grid behavior and column stacking implementation.
- Content Resolution
- Missing content: Verify container and contentlet resolution using correct identifiers and UUIDs.
- Component mapping fails: Check component registry setup and content type matching.
- Empty containers: Validate container data structure and handle empty states properly.
Debugging Tools#
- Browser Developer Tools
- Network Panel: Monitor API calls and responses from dotCMS endpoints.
- Console: Track page messages and editor communication through logging.
- Elements Inspector: Verify editor attributes and layout structure.
- dotCMS Tools
- Page Inspector: Examine page structure and container configurations.
- Template Editor: Verify layout setup and container placement.
- Content Search: Validate content availability and structure.
- Custom Debugging
- Event Logging: Track editor and page events for troubleshooting.
- Layout Visualization: Implement grid overlay for layout debugging.
- Message Monitor: Track communication between editor and page.
Error Resolution#
- API Errors
- Check authentication: Verify API token validity and permissions.
- Validate endpoints: Ensure correct API URLs and parameters.
- Review error responses: Check error messages and status codes for troubleshooting.
- Content Errors
- Verify content existence: Confirm content is published and available.
- Check permissions: Validate content access rights.
- Review content structure: Ensure content matches expected format.
- Editor Integration Errors
- Validate origin: Check cross-origin communication setup.
- Review message format: Verify correct message structure.
- Check event handling: Ensure proper event listener setup.
Support Resources#
- Documentation
- dotCMS API Documentation: Reference for all available endpoints and parameters.
- Implementation Guides: Step-by-step setup and integration instructions.
- Component Examples: Reference implementations for common scenarios.
- Community Resources
- dotCMS Forum: Community discussion and solution sharing.
- Issue Tracker: Known issues and workarounds.
- Code Examples: Reference implementations and solutions.
- Professional Support
- dotCMS Support: Official support channels for enterprise customers.
- Implementation Partners: Certified development partners.
- Training Resources: Official training materials and workshops.
- Development Resources
- Code Repository: Access to reference implementations.
- SDK Documentation: Framework-specific integration guides.
- Troubleshooting Guides: Common problems and solutions.