This page explains how to access Magnolia content from a client-side application. We use Magnolia's built-in REST API and a single-page AngularJS application.
We use the classic cars from the My first content app tutorial as sample content. The cars are typical structured content: each car has the same content type and same fields. Structured content is easy to enter, query, analyze and publish to multiple channels. You can publish it to a website or consume it from a client-side application like we do in this tutorial. With the REST API, you can access any Magnolia workspace.
If you want to use Swagger to test the REST endpoints, also install the magnolia-rest-tools module. It is not required to run the Angular app but it can be helpful during development.
When using magnolia-rest-tools, set the apiBasepath. The default value is most likely not correct for your Magnolia instance.
REST endpoints
The Magnolia REST module comes with preconfigured REST endpoints to read data from content apps. In this tutorial, we access content stored in the JCR so we use the following endpoints:
These two endpoints allow you to create, update and delete nodes and properties in any JCR workspace of your Magnolia instance.
Tip: You can also implement custom REST endpoints. You don't need them in this tutorial but custom endpoints are useful for:
Getting a very specific data structure which is different from what you get when using the preconfigured endpoints.
Exposing data from a custom content app which does not store the data in the JCR.
Getting data from more than one workspace within one request.
REST security
REST endpoints may be a security risk. Set permissions correctly. Read REST API security and define security for your specific requirements.
The preconfigured REST endpoints provide not only methods to read but create, edit and update data. Configure a specific role which meets your requirements. Add the role to the user group which should have access to the required REST methods. For instance, you could configure a role which can only read the nodes and properties of a specific workspace, then add this role to the anonymous user. During development it will help to add this new role to superuser too.
Install sample content
In this tutorial we use the Products app and the classic cars as sample content. The cars are stored in the products JCR workspace.
Maven is the easiest way to install the module. Add the following dependency to your bundle:
com.sun.jersey.api.client.ClientHandlerException: A message body reader for Java class info.magnolia.sys.confluence.plugin.artifactinfo.nexus.entities.SearchNGResponse, and Java type class info.magnolia.sys.confluence.plugin.artifactinfo.nexus.entities.SearchNGResponse, and MIME media type application/octet-stream was not found
Pre-built jars are also available for download. See Installing a module for help.
Error rendering macro 'artifact-resource-macro'
com.sun.jersey.api.client.ClientHandlerException: A message body reader for Java class info.magnolia.sys.confluence.plugin.artifactinfo.nexus.entities.SearchNGResponse, and Java type class info.magnolia.sys.confluence.plugin.artifactinfo.nexus.entities.SearchNGResponse, and MIME media type application/octet-stream was not found
If you don't have a Magnolia instance yet, follow Installing Magnolia and a content app, then come back here. You will end up with exactly what we want to have for this tutorial as well.
Grant permissions to access content via REST
Grant permission to read content from the workspace products to a new role.
Open the Security app.
Add a role read-products
In Access control lists, grant the new role:
Read-only access to / and its subnodes in the Products workspace
Read-only access to /read-products in the Userroles workspace.
In Web access, grant Get access to the path /.rest/nodes/v1/products*. Deny all others.
On the author instance, add the read-products role to the superuser system account.
On the author instance, publish the read-products role.
On the public instance, add the read-products role to the anonymous system account.
Log out and log back in to apply the new permissions to the currently logged-in user. This gets you a new session.
Test the app
Open the Products app installed by the app-tutorial module and get familiar with it.
Since the items in the app are cars let's talk about cars from now on. Try the tree, list and thumbnail views. Add a car of your own.
Test REST access
Request the app content with a REST call. Try the following request in your browser:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<node>
<identifier>ce39509e-e1d5-4cdd-b254-125948e2aeec</identifier>
<name>Aston-Martin-DB5</name>
<path>/cars/007/Aston-Martin-DB5</path>
<properties>
<property>
<multiple>false</multiple>
<name>description</name>
<type>String</type>
<values>
<value><p><strong>Movie:</strong>&nbsp;Goldfinger, GoldenEye, Casino Royale and Skyfall<br
/>
<strong>Year:</strong>&nbsp;1964, 1995, 2006 and 2012</p>
<p>The Aston Martin DB5 is one of the most famous cars in the world thanks to Oscar-winning special
effects expert John Stears, who created the deadly silver-birch DB5 for use by James Bond in Goldfinger
(1964). Although Ian Fleming had placed Bond in a DB Mark III in the novel, the DB5 was the company&#39;s
latest model when the film was being made.</p>
<p>A different Aston Martin DB5 (registration BMT 214A) was used in the 1995 Bond film, GoldenEye, in
which three different DB5s were used for filming. The BMT 214A also returned in Tomorrow Never Dies (1997) and
was set to make a cameo appearance in the Scotland-set scenes in The World Is Not Enough (1999), but these
were cut in the final edit. Yet another DB5 appeared in Casino Royale (2006), this one with Bahamian number
plates and left-hand drive (where the previous British versions had been right-hand drive).</p>
<p>Source: <a href="https://en.wikipedia.org/wiki/Aston_Martin_DB5">Wikipedia</a></p>
</value>
</values>
</property>
<property>
<multiple>false</multiple>
<name>title</name>
<type>String</type>
<values>
<value>Aston Martin DB5</value>
</values>
</property>
<property>
<multiple>false</multiple>
<name>image</name>
<type>String</type>
<values>
<value>jcr:4cd02639-167d-405a-923f-607aa20d0bc0</value>
</values>
</property>
</properties>
<type>mgnl:product</type>
</node>
We got an XML response but actually we want JSON. The nodes endpoint requires an HTTP request header to return JSON. Request the content with cURL so you can pass the desired return type.
{
"name": "Aston-Martin-DB5",
"type": "mgnl:product",
"path": "/cars/007/Aston-Martin-DB5",
"identifier": "ce39509e-e1d5-4cdd-b254-125948e2aeec",
"properties": [{
"name": "description",
"type": "String",
"multiple": false,
"values": ["<p><strong>Movie:</strong> Goldfinger, GoldenEye, Casino Royale and Skyfall<br />\n<strong>Year:</strong> 1964, 1995, 2006 and 2012</p>\n\n<p>The Aston Martin DB5 is one of the most famous cars in the world thanks to Oscar-winning special effects expert John Stears, who created the deadly silver-birch DB5 for use by James Bond in Goldfinger (1964). Although Ian Fleming had placed Bond in a DB Mark III in the novel, the DB5 was the company's latest model when the film was being made.</p>\n\n<p>A different Aston Martin DB5 (registration BMT 214A) was used in the 1995 Bond film, GoldenEye, in which three different DB5s were used for filming. The BMT 214A also returned in Tomorrow Never Dies (1997) and was set to make a cameo appearance in the Scotland-set scenes in The World Is Not Enough (1999), but these were cut in the final edit. Yet another DB5 appeared in Casino Royale (2006), this one with Bahamian number plates and left-hand drive (where the previous British versions had been right-hand drive).</p>\n\n<p>Source: <a href=\"https://en.wikipedia.org/wiki/Aston_Martin_DB5\">Wikipedia</a></p>\n"]
}, {"name": "title", "type": "String", "multiple": false, "values": ["Aston Martin DB5"]}, {
"name": "image",
"type": "String",
"multiple": false,
"values": ["jcr:4cd02639-167d-405a-923f-607aa20d0bc0"]
}]
}
If you installed the magnolia-rest-tools module, request the same with Swagger:
Play around a little bit to familiarize yourself with the Swagger tool. Figure out the correct parameters to get a JSON representation of all cars.
Create an AngularJS app
AngularJS is a popular Web application framework among front-end developers. In this tutorial we use an AngularJS app to access and render content from the Products app. We assume you know enough JavaScript to follow along.
The content items we render are cars. They are organized in the app like this:
carsList renders a list of all cars. Each car has a clickable label. Clicking on the label shows the car in the carDetail controller. The list controller also has two checkboxes to filter the list - one for each parent folder.
carDetail renders the detail of the selected car: title, description and image.
To avoid getting in trouble with the same-origin policy, run the Angular app on the same server as Magnolia. See how to overcome same-origin policy problem. Instead of a Freemarker template, keep it simple and just add a three static files to a light module.
CarsContentClient app
Start creating the Angular app in erics-cars.html. Add an ng-app directive to the body element which will wrap the two controllers:
Lines 5-7: References to JS and CSS files. We use dummy request parameters such as ?u=12 to bypass cache. We recommend that you use such dummy request parameters while developing. Alternatively, you could exclude content from the cache on the Magnolia instance but your browser may also cache the resources, particularly JS and CSS files. Once you're done with development, remove the request parameters.
Line 10: ng-app directive in the body element.
Request the file in the browser to make sure that Magnolia serves a static file. Use a dummy parameter ?foo=bar also in the request URL. It ensures we bypass cache on both server and client side.
To render a car's details we want a $scope object of the carDetail controller with the following properties:
carTitle
carDescription
imgUrl
Add the following HTML snippet to erics-car.html:
When you reload the file in the browser you may notice JavaScript errors. To see the errors, open the JavaScript console of your browser. The name of this "console" and how it is named depends on the browser you are using. For instance, on Google Chrome browser it is named DevTools.
globalData component
Add a component named globalData. Both controllers will use it. It has a property productPath which will be set by one controller and read by the other controller.
utils component
Add a component named utils. It provides some useful methods that both controllers can use. The carDetail controller will use the function #getPropertyValue.Note:
getPropertyValue(node, propertyName) is a convenience method to fetch a property from the JSON structure is returned by the standard nodes endpoint.
replaceAll(str, find, replace) manipulates strings. The carDetail controller uses this function just to style things a bit.
sanitize filter
The third little helper to add is the sanitize filter. Remember that the description property of the content item contains HTML, encoded HTML even. By default, Angular refuses to render JSON content that contains (HTML) markup. The sanitize filter makes sure that the (encoded) HTML is rendered properly as HTML. Internally the filter is using the angular module $sce.
carDetail controller
Finally, add the carDetail controller:Note:
Line 1: Custom componentsAPP_CONFIG, globalData and utils are injected.
Line 9: The controller executes an HTTP#get method on the nodes REST endpoint. The content item path is the selected item. It requests data for one content item as we did in testing REST access.
Line 8, 9: Computes a request URL for the REST call using configuration data from the globalData component.
Line 3ff: The HTTP#get call is defined as a callback function of $watch which is a value change listener for globalData.productPath. The REST call is executed initially and when globalData.productPath changes.
Reload the file in your browser again. Now you can see the detail of the Aston Martin DB5 in your browser. Cool! Isn't it?
carsList controller
The carsList controller renders a list of clickable span elements that contain car titles. The list can be filtered with checkboxes using the parent folders classics and 007 as pseudo-categories.
Add this HTML in erics-cars.html:The $scope of this controller has the following properties:
cars: An array of car items. Each car item has these properties:
path
category: actually a pseudo-category, the name of the parent folder such as classics or 007.
title: Title of the car
name: Node name of the item.
carCategories: An associative array (map) for the pseudo-categories. Each item has one property:
checked: Whether the pseudo-category is currently selected.
Here is the JavaScript code:Note:
Line 1: Again custom components APP_CONFIG, globalData and utils are injected.
Lines 5-7: selectCar is executed when you click a car of the list. The function sets globalData.productPath so it indirectly triggers the execution of the carDetail controller.
Lines 9-11: clickCategory maintains the $scope variable carCategories. Since this changes the state of a controller $scope variable, the UI gets repainted - the list items gets updated.
Line 3: The controller executes an HTTP#get method. Here the URI for the JSON call requests the cars root folder and contains the argument depth.
Final result:
Get the completed files
The three files in their final form are available in Magnolia's Git repository.
Option 1: Clone the Git repository
Clone the repository to get the complete file structure. Use a terminal, go to your light modules folder, and clone:
The JSON format provided by the nodesendpoint is not very handy. In this tutorial we managed to get the required properties with an extra JavaScript function in the utils component of the Angular app. But at some point you may want a JSON representation that you cannot get easily with the default JCR nodes and properties endpoints.
With a custom JSON service you can also get data from different JCR workspaces in one request. This cannot be done with the default endpoints for nodes and properties. A custom service therefore saves resources on the network.
Combine your content app with the Categorization module
The Products app used in this tutorial is very simple. We use folders to mimic car categories. Better use the Categorization module to add multiple categories.
Where to add the AngularJS app?
You can use or host the AngularJS app anywhere:
As static web resource served by Magnolia as seen in this tutorial
As part of a page or component template, in a template script
As a static or dynamic resource in any other server
Same-origin policy
If you want to run the AngularJS app on a distinct host – a host which has a different address than the Magnolia server which provides the REST API – you run into the same-origin policy problem. To overcome this problem some use specific Apache settings, see StackOverflow.
An elegant solution to solve the same-origin policy without changing Apache configuration is the Magnolia CORSFilter: