Previewing Structured Content From Content Apps
Jun 14, 2022
--
Previewing Content From Content Apps 1200x628

Previewing Structured Content From Content Apps

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

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

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

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

XML/HTML
  <!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

XML/HTML
  <!-- 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

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

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.

content_app_preview1

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

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

Bash
  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

YAML
  subApps:
 [...]
 preview:
   class: info.magnolia.blog.preview.preview.NodePreviewDetailDescriptor
 [...]

This is what the preview tab now looks like:

content_app_preview2

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.

About the author

Tobias Kerschbaum

Solution Architect, Magnolia

As a solution architect, Tobias works closely with customers and partners, sharing his knowledge and expertise. He helps organizations evaluate and understand how Magnolia can meet project requirements. He contributes to the project plan and ensures the right modules and technologies are chosen. Besides delivering tailored workshops, Tobias also gets involved when customers and partners need to implement new functionality or custom requirements.