Previewing Structured Content From Content Apps
Magnolia in just 12 minutes
Why not learn more about what Magnolia can do while you have a coffee?
Magnolia's headless CMS lets you manage content in a central hub and then use it on multiple touchpoints. Marketers and content authors can manage structured content using a Content app. Content apps make it easy to enter content such as products or events via a form.
The centralized management of content has several practical advantages. For example, changes only need to be made in one place to update the content in every frontend automatically. In addition, rights can be configured so that different departments have their own areas within the same content management system.
While Magnolia offers a visual editor to create and design pages, Content apps don’t offer a preview out of the box. A preview, however, can be very useful to check if the created content will look as desired, for example, a title wraps correctly, or an image looks right.
In this blog article, I would like to show how to create a preview for Content apps easily. You can rebuild this functionality in your environment by copying my code examples.
You need a web app, an additional Maven module, and a Magnolia Light Module:
blog-preview-webapp
blog-preview-config
blog-preview-light
Creating the Events Content App
First, we create a Content Type and Content app for events using Magnolia Light Development, our low-code approach. If you are new to Content Types and apps in Magnolia, I recommend reading the Content Types tutorial.
Creating the Content Type Model Definition
We create a very simple events content type definition following the Content Type documentation.
File: blog-preview-light/contentTypes/events.yaml
datasource:
workspace: events
autoCreate: true
model:
nodeType: event
properties:
- name: name
label: Event name
required: true
i18n: true
- name: location
label: Location
i18n: true
- name: startDate
label: Start date
type: Date
- name: endDate
label: End date
type: Date
- name: abstract
label: Abstract
i18n: true
- name: description
label: Description
type: richText
i18n: true
Creating the Events Content App
For the Events app, we use the previously defined Content Type. Let’s keep the app simple for now: point it to the events content type and assign it a name and a label.
File path: blog-preview-light/apps/events-app.yaml
!content-type:events
name: events-app
label: Events
We will extend the definition later.
Creating the Events Preview Page Template
To preview content outside of a web application, we need a page template for its presentation. We can use this page template for the preview only, or we can use the page template of the actual web app.
Since we haven’t created any page templates yet, we need to create a new template for the preview now.
Creating the Event Page Template Definition
First, we create a simple page definition without defining a user dialog for now.
File: blog-preview-light/templates/pages/event.yaml
title: Event
templateScript: /blog-preview-light/templates/pages/event.ftl
renderType: freemarker
Writing the Event Page Template Script
The second step is to create a simple template script that displays all event properties.
File: blog-preview-light/templates/pages/event.ftl
<!DOCTYPE html>
<html>
<head>
[@cms.page /]
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<style>
.container {/* Insert your Container style*/}
.element {/* Insert your Element style*/}
</style>
</head>
<body>
<!-- ${cmsfn.dump(content, 5, true)} uncomment to see all the properties -->
<div class="container">
<div class="element">Name: ${content.name}</div>
<div class="element">Location: ${content.location}</div>
<div class="element">StartDate: ${content.startDate}</div>
<div class="element">EndDate: ${content.endDate}</div>
<div class="element">Abstract: ${content.abstract}</div>
<div class="element">Description: ${cmsfn.decode(content).description!""}</div>
</div>
</body>
</html>
Updating the Maven Module Configuration
To use the same logic in the preview that will be used in the web app, we need to make two changes in the Maven module.
Updating Module.xml
We make the first change in module.xml by adding the new components using the following id pattern in the component definition: app-<app name>-<subapp name>.
File: blog-preview-config/src/main/resources/META-INF/magnolia/blog-preview-config.xml
<!-- For the event preview -->
<components>
<id>app-events-app-preview</id>
<component>
<type>info.magnolia.pages.app.detail.PageEditorStatus</type>
<implementation>info.magnolia.pages.app.detail.PageEditorStatus</implementation>
<scope>singleton</scope>
</component>
<component>
<type>info.magnolia.ui.framework.ContentClipboard</type>
<implementation>info.magnolia.pages.app.detail.action.clipboard.ComponentContentClipboard</implementation>
<scope>singleton</scope>
</component>
<component>
<type>info.magnolia.ui.vaadin.editor.PageEditorView</type>
<implementation>info.magnolia.ui.vaadin.editor.PageEditorViewImpl</implementation>
</component>
</components>
Bootstrapping URI2RepositoryMapping
We make the second change by adding the URI2RepositoryMapping. To activate the change when starting the environment we use the bootstrap mechanism of the Maven module.
File: blog-preview-config/src/main/resources/mgnl-bootstrap/blog-preview-config/config.server.URI2RepositoryMapping.mappings.event.yaml
'event':
'URIPrefix': '/events-app'
'handlePrefix': ''
'repository': 'events'
Creating the Preview Subapp for Events
Now that all necessary parts are in place, we can create the actual preview subapp for the events app. The following code changes are particularly interesting:
Action:
We define an action that calls our preview subapp defined below.
Action bar:
In the Action bar, we activate the previously defined ShowPreview action.
Detail subapp:
In the details subapp, we add a hidden property 'mgnl:template' with the default value 'blog-preview-light:pages/event' for each newly created event. This is the magic that allows us to use the rendering engine of the web application.
Preview subapp:
In the preview subapp, we define the ‘extensionViews’ from the Magnolia Pages app as well as some actions in the action bar. The 'extensionViews' are responsible for displaying, for example, the language changer in the lower area of the preview.
File: blog-preview-light/templates/pages/event.yaml
!content-type:events
name: events-app
label: Events
subApps:
browser:
actions:
showPreview:
$type: openDetailSubappAction
label: Show preview
icon: icon-view
viewType: view
appName: events-app
subAppName: preview
availability:
writePermissionRequired: true
nodeTypes:
event: event
actionbar:
sections:
item:
groups:
addActions:
items:
showPreview: {}
detail:
label: Event
form:
properties:
mgnl:template:
$type: hiddenField
defaultValue: 'blog-preview-light:pages/event'
preview:
class: info.magnolia.pages.app.detail.PageDetailDescriptor
extensionViews:
title:
class: info.magnolia.pages.app.detail.extension.PageTitleViewDefinition
status:
class: info.magnolia.pages.app.detail.extension.PublishingStatusViewDefinition
link:
class: info.magnolia.pages.app.detail.extension.NativePagePreviewLinkViewDefinition
language:
class: info.magnolia.pages.app.detail.extension.LanguageSelectorViewDefinition
itemProvider:
$type: jcrNodeFromLocationProvider
actions:
activate:
$type: jcrCommandAction
icon: icon-publish
catalog: default
command: publish
params:
recursive: true
availability:
writePermissionRequired: true
rules:
isPublishable: &isPublishable
$type: jcrPublishableRule
deactivate:
$type: jcrCommandAction
icon: icon-unpublish
catalog: default
command: unpublish
availability:
writePermissionRequired: true
rules:
isPublishable: *isPublishable
notDeleted:
$type: jcrIsDeletedRule
negate: true
isPublished:
$type: jcrPublishedRule
actionbar:
sections:
previewActions:
label: Preview actions
groups:
publish:
items:
- name: activate
- name: deactivate
availability:
rules:
inPreview:
class: info.magnolia.pages.app.detail.action.availability.IsPreviewRuleDefinition
Congratulations! If you now start your server and open the Events app, you can preview your events.
You might notice, though, that the preview tab does not display the name of the event node. Let’s see how we can fix this.
Displaying the Node Name in the Preview
To show the node name in the preview tab, we have to leave the world of Light Development and do a little Java customization. This change is necessary because the default behaviour of title generation does not work for Content Types.
Creating a Custom DetailDescriptor
First, we create a DetailDescriptor to make our subapp known.
File: blog-preview-config/src/main/java/info/magnolia/blog/preview/preview/NodePreviewDetailDescriptor.java
package info.magnolia.blog.preview.preview;
import info.magnolia.pages.app.detail.PageDetailDescriptor;
public class NodePreviewDetailDescriptor extends PageDetailDescriptor {
public NodePreviewDetailDescriptor() {
setSubAppClass(NodePreviewDetailSubApp.class);
}
}
Creating a Custom DetailSubApp
In the DetailSubApp we override the getCaption method to create the node title in a preview tab.
File: blog-preview-config/src/main/java/info/magnolia/blog/preview/preview/NodePreviewDetailSubApp.java
package info.magnolia.blog.preview.preview;
import info.magnolia.pages.app.detail.PageDetailDescriptor;
import info.magnolia.pages.app.detail.PageDetailSubApp;
import info.magnolia.pages.app.detail.PageEditorStatus;
import info.magnolia.pages.app.detail.context.MoveComponentContext;
import info.magnolia.ui.api.app.SubAppContext;
import info.magnolia.ui.contentapp.detail.ContentDetailSubApp;
import info.magnolia.ui.vaadin.editor.PageEditorView;
import javax.inject.Inject;
public class NodePreviewDetailSubApp extends PageDetailSubApp {
private final PageEditorStatus pageEditorStatus;
@Inject
public NodePreviewDetailSubApp(SubAppContext subAppContext, PageDetailDescriptor subAppDescriptor, ContentDetailSubApp.LocationContext locationContext, MoveComponentContext moveComponentContext, PageEditorStatus pageEditorStatus, PageEditorView pageEditorView) {
super(subAppContext, subAppDescriptor, locationContext, moveComponentContext, pageEditorStatus, pageEditorView);
this.pageEditorStatus = pageEditorStatus;
}
@Override
public String getCaption() {
return pageEditorStatus.getNodePath().substring(pageEditorStatus.getNodePath().lastIndexOf('/') + 1).trim();
}
}
Updating the Preview Subapp Class
Finally, we change the events app’s definition. Instead of using the default class info.magnolia.pages.app.detail.PageDetailDescriptor we define the new class info.magnolia.blog.preview.preview.NodePreviewDetailDescriptor in line 33.
File: blog-preview-light/templates/pages/event.yaml
subApps:
[...]
preview:
class: info.magnolia.blog.preview.preview.NodePreviewDetailDescriptor
[...]
This is what the preview tab now looks like:
Creating a Preview for Content App is Easy and Helps Authors
With relatively simple means, it is possible to preview any content quickly. A preview for Content apps can really improve the editorial experience. Authors can check how their content will look in just one click, saving time and nerves.