Magnolia 5.4 reached end of life on November 15, 2018. This branch is no longer supported, see End-of-life policy.
In this tutorial you create your first content app. A content app is a special app type that is well suited for managing custom content items such as products. The items in this example are antique cars but you can use any items you wish.
Your content app needs to be deployed as a Magnolia module. Choose from these options depending on your skill level.
Choose this option if you know how to work with a Magnolia project and Git. You get a project that you can edit it in your IDE and customize to your needs.
app-tutorial
repository.git clone https://git.magnolia-cms.com/scm/documentation/app-tutorial.git
Choose this option if you are new to Magnolia. Download the module JAR and follow the standard module installation instructions. You get the complete content app and can learn how it works.
<CATALINA_HOME>/webapps/<contextPath>/WEB-INF/lib
folder. Typically this is <CATALINA_HOME>/webapps/magnoliaAuthor/WEB-INF/lib
.If you already have a module, and want to quickly create a new App, you can use the Groovy App:
/createAppScript
.app_display_name
, here you can specify the name of your app
parent_module
, this is where the app will be created (enter your module name)
app_group
, the group on the app launcher in which your app should display.
app_icon
, the icon of your app
app_repository
, here you can keep the default repository
app_folder_support
, if you want to be able to create folders within your app
The module contains the following source files:
magnolia-module-app-tutorial.xml
products.yaml
config.modules.ui-admincentral.config.appLauncherLayout.groups.edit.apps.products.xml
products.cars.xml
dam.cars.xml
app-tutorial-nodetypes.xml
app-products-messages_en.properties
app-products-messages_es.properties
The app is configured in YAML in products.yaml
. Equivalent JCR configuration examples are provided below.
pages
since a Pages app already exists.appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor # subapps definition
Node name | Value |
---|---|
modules | |
my-module | |
apps | |
products | |
appClass | info.magnolia.ui.contentapp.ContentApp |
class | info.magnolia.ui.contentapp.ContentAppDescriptor |
The app launcher can only be configured in the JCR.
In the app launcher layout we add the app to the Edit group.
Node name |
---|
modules |
ui-admincentral |
config |
appLauncherLayout |
groups |
edit |
apps |
pages |
assets |
contacts |
products |
Go to the app launcher and verify that you can see the Products app icon.
If you move the app to another group, log out and back in to see the change.
The browser
subapp allows you to view and organize items. It is part of every content app.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: browser: subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor
Node name | Value |
---|---|
modules | |
app-tutorial | |
apps | |
products | |
subApps |
|
browser |
|
class | info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor |
subAppClass | info.magnolia.ui.contentapp.browser.BrowserSubApp |
A content connector tells the app where the content resides. In this case we store products in the repository so we use a JCR content connector. The configuration identifies the workspace and a path. It also configures the node type the app operates on.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: browser: subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor contentConnector: includeProperties: false workspace: products rootPath: / defaultOrder: jcrName nodeTypes: - name: mgnl:product icon: icon-items - name: mgnl:folder icon: icon-folder
Node name | Value |
---|---|
subApps | |
browser |
|
contentConnector |
|
nodeTypes |
|
product | |
icon | icon-items |
name | mgnl:product |
folders | |
icon | icon-folder |
name | mgnl:folder |
defaultOrder | jcrName |
includeProperties | false |
rootPath | / |
workspace | products |
In this app, the workbench displays two node types: mgnl:folder
and mgnl:product
.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: browser: subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor # contentConnector definition workbench: dropConstraintClass: info.magnolia.ui.workbench.tree.drop.AlwaysTrueDropConstraint editable: false
Node name | Value |
---|---|
browser |
|
workbench |
|
dropConstraintClass | info.magnolia.ui.workbench.tree.drop.AlwaysTrueDropConstraint |
editable | false |
A content view defines how the content is displayed to the user. Under workbench
, we have four views: tree, list, thumbnail and search. The tree view defines columns: Name, Status and Modification date. The other views extend the tree view, displaying the same columns.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: browser: subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor # contentConnector definition workbench: dropConstraintClass: info.magnolia.ui.workbench.tree.drop.AlwaysTrueDropConstraint editable: false contentViews: - name: tree class: info.magnolia.ui.workbench.tree.TreePresenterDefinition columns: &myColumns - name: name editable: true sortable: true propertyName: jcrName class: info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition - name: status width: 45 displayInChooseDialog: false formatterClass: info.magnolia.ui.workbench.column.StatusColumnFormatter class: info.magnolia.ui.workbench.column.definition.StatusColumnDefinition - name: moddate width: 160 sortable: true displayInChooseDialog: false formatterClass: info.magnolia.ui.workbench.column.DateColumnFormatter propertyName: mgnl:lastModified class: info.magnolia.ui.workbench.column.definition.MetaDataColumnDefinition - name: list class: info.magnolia.ui.workbench.list.ListPresenterDefinition columns: *myColumns - name: thumbnail class: info.magnolia.ui.workbench.thumbnail.ThumbnailPresenterDefinition - name: search class: info.magnolia.ui.workbench.search.SearchPresenterDefinition columns: *myColumns
Node name | Value |
---|---|
workbench | |
contentViews |
|
tree |
|
columns |
|
name |
|
class | info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition |
editable | true |
label | Product name |
propertyName | jcrName |
sortable | true |
status | |
class | info.magnolia.ui.workbench.column.definition.StatusColumnDefinition |
displayInChooseDialog | false |
formatterClass | info.magnolia.ui.workbench.column.StatusColumnFormatter |
label | Status |
width | 45 |
moddate | |
class | info.magnolia.ui.workbench.column.definition.MetaDataColumnDefinition |
displayInChooseDialog | false |
formatterClass | info.magnolia.ui.workbench.column.DateColumnFormatter |
label | Modification date |
propertyName | mgnl:lastModified |
sortable | true |
width | 160 |
class | info.magnolia.ui.workbench.tree.TreePresenterDefinition |
list |
|
columns |
|
extends | ../../tree/columns |
class | info.magnolia.ui.workbench.list.ListPresenterDefinition |
thumbnail | |
class | info.magnolia.ui.workbench.thumbnail.ThumbnailPresenterDefinition |
search | |
columns |
|
extends | ../../tree/columns |
class | info.magnolia.ui.workbench.search.SearchPresenterDefinition |
Here's the tree view:
Test the other views too. Search is case sensitive by default.
Actions allow you to add, edit and delete folders and products. Each action adheres to an action definition.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: browser: subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor # contentConnector definition # workbench definition actions: addProduct: subAppId: detail icon: icon-add-item nodeType: mgnl:product appName: products class: info.magnolia.ui.contentapp.detail.action.CreateItemActionDefinition availability: root: true nodeTypes: - mgnl:folder addFolder: icon: icon-add-folder class: info.magnolia.ui.framework.action.AddFolderActionDefinition availability: root: true editFolder: icon: icon-edit dialogName: ui-framework:folder class: info.magnolia.ui.framework.action.OpenEditDialogActionDefinition deleteFolder: icon: icon-delete class: info.magnolia.ui.framework.action.DeleteItemActionDefinition editProduct: subAppId: detail icon: icon-edit appName: products class: info.magnolia.ui.contentapp.detail.action.EditItemActionDefinition availability: nodeTypes: - mgnl:product deleteProduct: icon: icon-delete class: info.magnolia.ui.framework.action.DeleteItemActionDefinition
Node name | Value |
---|---|
browser | |
actions |
|
addProduct |
|
availability | |
nodeTypes | |
folder | mgnl:folder |
root | true |
appName | products |
class | info.magnolia.ui.contentapp.detail.action.CreateItemActionDefinition |
icon | icon-add-item |
nodeType | mgnl:product |
subAppId | detail |
addFolder |
|
availability | |
root | true |
class | info.magnolia.ui.framework.action.AddFolderActionDefinition |
icon | icon-add-folder |
editFolder |
|
class | info.magnolia.ui.framework.action.OpenEditDialogActionDefinition |
dialogName | ui-framework:folder |
icon | icon-edit |
deleteFolder | |
class | info.magnolia.ui.framework.action.DeleteItemActionDefinition |
icon | icon-delete |
editProduct | |
availability | |
nodeTypes | |
product | mgnl:product |
appName | products |
class | info.magnolia.ui.contentapp.detail.action.EditItemActionDefinition |
icon | icon-edit |
subAppId | detail |
deleteProduct | |
class | info.magnolia.ui.framework.action.DeleteItemActionDefinition |
icon | icon-delete |
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: browser: subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor # contentConnector definition # workbench definition # actions definition actionbar: defaultAction: editProduct sections: - name: root groups: - name: addActions items: - name: addProduct - name: addFolder availability: nodes: false root: true - name: folder groups: - name: addActions items: - name: addProduct - name: addFolder - name: editActions items: - name: editFolder - name: deleteFolder availability: nodeTypes: - mgnl:folder - name: product groups: - name: editActions items: - name: editProduct - name: deleteProduct availability: nodeTypes: - mgnl:product
Node name | Value |
---|---|
browser |
|
actionbar |
|
sections |
|
root | |
groups | |
addActions | |
items | |
addProduct | |
addFolder | |
availability | |
nodes | false |
root | true |
folder | |
groups | |
addActions | |
items | |
addProduct | |
addFolder | |
editActions | |
items | |
editFolder | |
deleteFolder | |
availability | |
nodeTypes | |
folder | mgnl:folder |
product | |
groups | |
editActions | |
items | |
editProduct | |
deleteProduct | |
availability | |
nodeTypes | |
product | mgnl:product |
defaultAction | editProduct |
Image provider is a component that renders images used in apps. It generates the portrait image at the bottom of the action bar and the thumbnails for the thumbnail view.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: browser: subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor # contentConnector definition # workbench definition # actions definition # actionbar definition imageProvider: class: info.magnolia.dam.app.ui.imageprovider.DamLinkImageProviderDefinition damLinkPropertyName: image
Node name | Value |
---|---|
browser | |
imageProvider | |
class | info.magnolia.dam.app.ui.imageprovider.DamLinkImageProviderDefinition |
damLinkPropertyName | image |
Select an item in the browser subapp to test the preview. The preview image is displayed at the bottom of the action bar.
A detail subapp allows you to edit a product. It is configured with a subapp descriptor just like a browser subapp but the classes are different.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: # browser subapp definition detail: subAppClass: info.magnolia.ui.contentapp.detail.DetailSubApp class: info.magnolia.ui.contentapp.detail.DetailSubAppDescriptor
Node name | Value |
---|---|
subApps |
|
detail |
|
class | info.magnolia.ui.contentapp.detail.DetailSubAppDescriptor |
subAppClass | info.magnolia.ui.contentapp.detail.DetailSubApp |
A JCR content connector is also needed in the detail subapp. Here the definition is simpler. You just need to name the workspace.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: # browser subapp definition detail: subAppClass: info.magnolia.ui.contentapp.detail.DetailSubApp class: info.magnolia.ui.contentapp.detail.DetailSubAppDescriptor contentConnector: workspace: products
Node name | Value |
---|---|
subApps | |
detail |
|
contentConnector |
|
workspace | products |
The rootPath
property is omitted because the default value is /
which is the same as in browser subapp content connector. If you define these properties they must have the same value in both subapps. However, since /
is the default value you could omit the property in both subapps.
We highly recommend using the same rootPath
in both the detail and browser sub-apps, to benefit from our standard set of actions provided out of the box.
detail
subapp. Define the node types the editor edits, a form for editing them, and actions for saving the edit.appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: # browser subapp definition detail: subAppClass: info.magnolia.ui.contentapp.detail.DetailSubApp class: info.magnolia.ui.contentapp.detail.DetailSubAppDescriptor # contentConnector definition editor: nodeType: icon: icon-items name: mgnl:product
Node name | Value |
---|---|
detail | |
editor | |
nodeType | |
icon | icon-items |
name | mgnl:product |
Your app can edit any kind of content. What you plan to edit determines what fields you need on the form. In this tutorial the items are products so we create a form with three fields: product name, photo and photo credit.
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: # browser subapp definition detail: subAppClass: info.magnolia.ui.contentapp.detail.DetailSubApp class: info.magnolia.ui.contentapp.detail.DetailSubAppDescriptor # contentConnector configuration editor: # nodeType definition form: tabs: - name: product fields: - name: jcrName class: info.magnolia.ui.form.field.definition.TextFieldDefinition - name: title class: info.magnolia.ui.form.field.definition.TextFieldDefinition - name: image class: info.magnolia.ui.form.field.definition.LinkFieldDefinition targetWorkspace: dam appName: assets label: Select image identifierToPathConverter: class: info.magnolia.dam.app.assets.field.translator.AssetCompositeIdKeyTranslator contentPreviewDefinition: contentPreviewClass: info.magnolia.dam.app.ui.field.DamFilePreviewComponent - name: description class: info.magnolia.ui.form.field.definition.RichTextFieldDefinition
Node name | Value |
---|---|
editor |
|
form |
|
tabs |
|
product |
|
fields |
|
jcrName |
|
class | info.magnolia.ui.form.field.definition.TextFieldDefinition |
name | jcrName |
title |
|
class | info.magnolia.ui.form.field.definition.TextFieldDefinition |
name | title |
image | |
identifierToPathConverter | |
class | info.magnolia.dam.app.assets.field.translator.AssetCompositeIdKeyTranslator |
contentPreviewDefinition | |
contentPreviewClass | info.magnolia.dam.app.ui.field.DamFilePreviewComponent |
appName | assets |
class | info.magnolia.ui.form.field.definition.LinkFieldDefinition |
label | Select image |
targetWorkspace | dam |
description | |
class | info.magnolia.ui.form.field.definition.RichTextFieldDefinition |
Commit and cancel are generic reusable actions that save the form data. They are defined under the detail
subapp. The corresponding dialog buttons are in the editor (see above).
appClass: info.magnolia.ui.contentapp.ContentApp class: info.magnolia.ui.contentapp.ContentAppDescriptor subApps: # browser subapp definition detail: subAppClass: info.magnolia.ui.contentapp.detail.DetailSubApp class: info.magnolia.ui.contentapp.detail.DetailSubAppDescriptor # contentConnector definition editor: # nodeType definition # form definition actions: - name: commit - name: cancel
Node name | Value |
---|---|
detail |
|
actions |
|
commit |
|
cancel |
|
Create a few items. Verify that you can edit existing items too.
Here are ways to create your own content app:
apple
with orange
.This method works well if one of Magnolia's native apps does almost what you need. You don't need to change much. Extend the native app to get all its functionality and add something special. For example, configure a special Pages app that displays only one website branch. If editors mostly work in that branch the app saves them from having to navigate there.
See also 3.3.5 Create an app in Magnolia Academy.
Here are ideas for enhancing your content app and making it ready for production use:
Add export and import actions so you can export products into XML. See how the actions are implement in the Contacts app. Copy the action configuration to your app. The Magnolia UI framework provides generic export and import actions that you can use out of the box.
/modules/contacts/apps/contacts/subApps/browser/actions/export /modules/contacts/apps/contacts/subApps/browser/actions/import /modules/contacts/apps/contacts/subApps/browser/actionbar/sections/root/groups/importExportActions /modules/contacts/apps/contacts/subApps/browser/actionbar/sections/contact/groups/importExportActions /modules/contacts/apps/contacts/subApps/browser/actionbar/sections/folder/groups/importExportActions
To add publishing actions:
activate
and deactivate
actions so that you can publish products to the public instance. Copy the actions from the Contacts app.activate
and deactivate
actions in the action bar. You can copy these also from the Contacts app. products
workspace so that public instances receive the content. Don't forget to install your module also on the public instance! You may not need the app on the public instance but you will need the
products
workspace so that products can be activated.
Prevent editors from deleting products with a single click. The best practice is to mark an item for deletion and require the editor to publish the change:
See how the Contacts app handles this and replicate the actions in your app. See also how the info.magnolia.ui.api.availability.IsNotDeletedRule
action availability rule is used in the activate action to make sure that the activatable item is not marked for deletion.
You may want to display product images on the website or in a shop. Add a URI2RepositoryMapping for the products
workspace. The mapping tells Magnolia to serve content from the products
workspace when the request URL contains the /products
prefix.
To add a URI2RepositoryMapping:
/server/URI2RepositoryMapping/mappings
.Map the /products URI prefix to the products
workspace.
Node name | Value |
---|---|
server |
|
URI2RepositoryMapping |
|
mappings |
|
products |
|
URIPrefix | /products |
handlePrefix |
|
repository | products |
products
node and its properties to the public instance.anonymous
role permission to read the products
workspace. This allows visitors to view the images on the public instance.
Photo credits:
Aston Martin DB5, Aston Martin Works
Aston Martin V8, Beltane43, Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)