Divide and Conquer: Rendering Structured Web Content with the Asset Publisher

A FAQ about Application Display Templates (ADTs) is how to render the fields in an structured web content when it is listed on an Asset Publisher portlet. Paulo Fernandes proposed in a blog entry a widely used approach based on parsing (via iteration or xpath) the underlying XML. As an alternative, this time I'll show you a divide-and-conquer solution for this use-case.

Introducing the Liferay T-Shirt Shop

To help you better understand the proposal, throughout  the description we will make use of a practical example: the UI of a Liferay T-shirt shop built with Asset Publisher and web content.

Liferay T-shirt Shop

Our items (cool Liferay tees) consist on a title, a description, a price and a high-resolution image available in our file respository. Thus, our first step is to create a web content structure Shop Item with these fields (actually, only the price and the image since the web content already provides a title and a description). 

Shop Item Web Content Structure

To populate our shop with items, we just need to create web content articles of this type and fill in the fields for each T-shirt.

So far so good, but now comes the tricky part: we want to list our items in the frontpage of our site, and the Asset Publisher (Liferay's list-it-all portlet) seems the best option, but some of our information lives deep in the web content structure. Paulo demonstrated a way to access it with ADTs but, is it the only one?

Let the cobbler stick to his last

ADTs are very handy when it comes to customize how the Asset Publisher renders a list of content. Customization can go from simple bullets to displaying the items on a map. The ADT editor helps us list assets and access their common fields (title, description, links...), which is enough in most cases. If you need to access some type-specific information of an asset, you can use its assetRender instance to retrieve the original model object and access its full information. However, accessing the fields of an structured content such as a web content from an ADT is not straightforward and requires - as shown by Paulo - a knowledge of the underlying XML data.

Web content templates, on their side, are very good in giving you access to the fields of an structured web content. The palette in the web template editor contains a list with the fields declared in the structure, and by selecting them, a basic code snippet to render the field is added to the body of the template.

Another nice thing about web content templates is that you can create as many of them as you want for the same structure. In other words, you can define multiple ways of rendering the same type of content. Finally, you can define generic web content templates that could be use to render different types of content, as long as they have common field names.

So why not using each type of template (ADTs and web content templates) for what they are best at?

Leveraging the journal-article Taglib

In order to connect these two worlds (the list rendering - i.e. the ADT - and the content rendering - i.e. the web content template) we will use the journal-article taglib, available in the liferay-journal taglib namespace.

First let's define how each shop item should be displayed individually. For this purpose, we create a web content template Shop Item Card for the Shop Item structure:

<@liferay_frontend["vertical-card"]
    cssClass="item-preview"
	imageUrl=image.getData()
	title=.vars['reserved-article-title'].data
>
    <@liferay_frontend["vertical-card-footer"]>
	    USD ${price.getData()}
	    <p>${.vars["reserved-article-description"].data}</p>
    </@>
</@>

In this small yet powerfull web content template, we use some Liferay taglibs to create a card-based UI for our items. The document image and the price are easily accessed, since they've been injected as variables by the web content template engine. Notice that accessing the default title and description is a bit tricky in Freemarker, due to the dashes (another FAQ, that's why I used those in the example ;-)).

Second, we define how the shop item list will look like. Since ADTs are good for lists, we create a new Asset Publisher Application Display Template:

<#if entries?has_content>
     <div class="row row-spacing uxgl-vertical-card">
   <#list entries as curEntry>
   <#assign 
       assetRenderer = curEntry.getAssetRenderer()
       journalArticle = assetRenderer.getAssetObject()      
   />

<div class="col-md-3">
       <@liferay_journal["journal-article"]
                    articleId=journalArticle.getArticleId()
                    ddmTemplateKey="31029"
                    groupId=journalArticle.getGroupId()
                />
            </div>
   </#list>
</div> 
</#if>

A few lines of code, but again lots of things happening. The ADT creates a grid (1 row, fluid) and iterates the items, adding one column for each entry. It extracts the original web content (aka JournalArticle) from the entry and (here comes the magic) uses the journal-article taglib to fill the column with the result of rendering the web content with the previously created web content template.

The ddmTemplateKey field determines which web content template renders the web content. You can obtain this value in the details section of the web content template editor.

Template Key

Finally, add an Asset Publisher portlet to a page and select your ADT in the display settings configuration. As a result, we'll see our shop UI rendered thanks to the friendly collaboration of an application display template and a web content display:

Result

And it doesn't end here...

This divide-and-conquer strategy allows to customize the grid and the item UI separately. For instance, by changing the column style in the ADT we can modify the size of the items in the grid:

As for the item, you can create as many different web content templates as you want and shift from one to another by simply changing the ddmTemplateKey in the ADT. You could even create templates to render only certain fields of the structure, and then combining then as you want in the ADT. The combinations are endless and as you have seen, it only takes a few lines of code to create catching results. I encourage you to try it out and share your experiences!

Troubleshooting

This example has been built with Liferay 7 GA4. Make sure the Liferay Taglib is available in your Liferay 7 CE / DXP installation (Go to Control Panel > Apps > App Manager and search for it). If it is not, you can obtain it from the Liferay Portal source repository.

In the web content structure, be aware of the field names in the settings tab of the field editor. Those are the ones that must go in the web content template.

In the ADT, we assume that all the items are web content with the structure Shop Item. You can enforce this by configuring the asset type in the Asset Publisher portlet:

Asset Type selection

Alternatively, you could add some code-level checking to the ADT.

If you find any other issues, please add a comment to this entry and I'll be glad to help you :D

Blogs
Question: where can I find the accepted definitions for @liferay_frontend?
I mean other than vertical-card and vertical-card-footer what are the other options?
Where can I find documentation about this?
Thanks
Hi Mirto,
You can find the javadocs for the frontend taglibs here: https://docs.liferay.com/ce/apps/foundation/latest/javadocs/com/liferay/frontend/taglib/.
And the TLD is here:
https://github.com/liferay/liferay-portal/blob/7.0.x/modules/apps/foundation/frontend-taglib/frontend-taglib/src/main/resources/META-INF/liferay-frontend.tld
Hi, thanks for this great article. I'm missing just two important things in it:

1) It should have been stated clearly *right in the first paragraph* that this article is not quite applicable to older versions of Liferay, or is it? We haven't found an easy way to do something similar prior to Liferay 7.

2) What if one would need to *link to the details of the rendered items* from inside the content rendered by the WC template. We've got the item's page URL available in the AP template, but how can we read it in the called WC template?
For the link to detail, you _could_ put a token (like {{link-to-detail}} ) in the web content article template and then do a replace of that token with the link from the ADT code.

The ADT (and the asset entry) give you a little more power to build the detail link so that worked better for us in our case while using a similar approach with velocity in 6.2.
Hi Petr,

1) Certainly, the article contains code examples that are directly applicable only to Liferay 7. But you can achieve this on 6.2, too. In that version, the journal-article taglib was located in the liferay-ui namespace. Thus, you'd have to use liferay_ui["journal-article"] instead of "liferay_journal["journal-article"]. The card taglibs are new in Liferay 7, but you can use any bootstrap similar components instead. (http://getbootstrap.com/2.3.2/components.html).
2) You should be able to obtain the AssetRenderer from within the WC template, and obtain the URLs with it.
Hi, great article. I'm wondering if its possible to set some sort of context variable in the red boxed adt (the one for assetpublisher) and use it in the web content model (the green boxed one). Using an asset publisher it's common to build a viewUrl from asset like this:

viewURL = assetPublisherHelper.getAssetViewURL(renderRequest, renderResponse, assetRenderer, entry, true)

However I don't know how to render this information in the web content model. There is some mechanism to pass information between these two ftls?
I solved using the following steps:

1) in the asset publisher's ADT I've used:
<#assign serviceContext = staticUtil["com.liferay.portal.kernel.service.ServiceContextThreadLocal"].getServiceContext() />
<#if entries?has_content>

<#list entries as entry>
<#assign
assetRenderer = entry.getAssetRenderer()
journalArticle = assetRenderer.getAssetObject()

viewURL = assetPublisherHelper.getAssetViewURL(renderRequest, renderResponse, assetRenderer, entry, !stringUtil.equals(assetLinkBehavior, "showFullContent"))

temp = serviceContext.setAttribute("my_view_url", viewURL)
/>
<@liferay_journal["journal-article"]
articleId=journalArticle.getArticleId()
ddmTemplateKey="000000"
groupId=journalArticle.getGroupId()
/>
</#list>
</#if>

2) in web content template I've used:
...
<#assign
serviceContext = staticUtil["com.liferay.portal.kernel.service.ServiceContextThreadLocal"].getServiceContext()
viewURL = serviceContext.getAttribute("my_view_url")
/>
${viewURL}
...
Do you think it's a good way to pass information between the two templates?

Thanks
Hi Sandro:
I'm trying to reproduce your example but I get an error in the structure template when accessing to the serviceContext's attribute:

The following has evaluated to null or missing:
==> serviceContext.getAttribute("my_view_url") [in template "20116#20152#43209" at line 6, column 22]

There are no errors when processing the ADT, just in the structure template. What is it missing?

Thank you!
Hi Santiago,

the line you reported should be put in the model template, but you have to put a

temp = serviceContext.setAttribute("my_view_url", "some value")

in the assetpublisher template, processed before the model one, otherwise the attribute will not be found in the model template
Hello.. Thanks for this great article. But I have one question: What if I want to get the html code that tag creates itself? Is there a way to obtain the html code fragment of the asset rendered by its template and store it - for example in a variable?

Thanks alot in advance!
This is an interesting idea. The only thing I'm not thrilled about is that hardcoded DDMTemplateKey . If you are collaborating with someone, or need to move it from a dev system to production it's certainly going to change. I wonder, can a web content template tell what portlet it is being rendered in? If so then it would be better to code one template with
<#if isAssetPublisher >
...
<#else> <#-- is WCD -->
...
</#if>

sort of logic.