Accessing a Web Content Structure from Application Display Template

In my experience working with Liferay projects, the most used component is Web Content, not only the Basic Web Content. A lot of times the customer needs to create a structure and a template to achieve his goal. In previous Liferay Portal version (aka 6.1) we need to create a hook to create new Asset Display View, but with Liferay Portal 6.2, we can create a simple Application Display Template (ADT) for Asset Publisher and everything is working in a great way and easily.

For this article I created a simple structure with the following fields: Title, SubTitle and Content.

Let's create the ADT to show the Title, SubTitle and a Link to read the full structure.

 

Solution

The following code access the Web Content Structure in two different ways. Choose which is better to you.

#if (!$entries.isEmpty())

    #foreach ($entry in $entries)
        #set($renderer = $entry.getAssetRenderer() )
        #set($className = $renderer.getClassName() )

        #if( $className == "com.liferay.portlet.journal.model.JournalArticle" )
            #set( $journalArticle = $renderer.getArticle() )
            #set( $document = $saxReaderUtil.read($journalArticle.getContent()) )
            #set( $rootElement = $document.getRootElement() )


            ## first alternative            
            #foreach( $dynamicElement in $rootElement.elements() )
                #if( "subTitle" == $dynamicElement.attributeValue("name") )
                    #set( $subTitle1 = $dynamicElement.element("dynamic-content").getText() )
                    first alternative -> $subTitle1 <br />
                #end
            #end


            ## second alternative
            #set( $xPathSelector = $saxReaderUtil.createXPath("dynamic-element[@name='subTitle']") )
            #set( $subTitle2 = $xPathSelector.selectSingleNode($rootElement).getStringValue() ) 
            second alternative -> $subTitle2 <br />
        #end
    #end
#end

Choose which one is better to you. A improve that you could do for this code is check, if the WebContent (JournalArticle) is the same that the Structure that you are accessing. I didn't to that because I have only one Strucutre.

Let's see the final code.

#if (!$entries.isEmpty())
    <div class="news">
    #foreach ($entry in $entries)
        #set($renderer = $entry.getAssetRenderer() )
        #set($className = $renderer.getClassName() )
        #if( $className == "com.liferay.portlet.journal.model.JournalArticle" )
            #set( $journalArticle = $renderer.getArticle() )
            #set( $document = $saxReaderUtil.read($journalArticle.getContent()) )
            #set( $rootElement = $document.getRootElement() )
            #set( $xPathSelector = $saxReaderUtil.createXPath("dynamic-element[@name='subTitle']") )
            #set( $subTitle = $xPathSelector.selectSingleNode($rootElement).getStringValue() )
            #set( $link = $renderer.getURLViewInContext($renderRequest, $renderResponse, '') )
            
            <div class="new">
                <h1 class="title">$entry.getTitle($locale)</h1>
                <h3 class="sub-title">$subTitle</h3>
                <p class="read-more">
                    <a href="$link">Read More</a>
                </p>
            </div>
        #end
    #end
    </div>
#end

 

I hope that this code can help you more than is helping me. If you have any trouble, please post in our forum.

 

More information about Application Display Template

https://www.liferay.com/pt/web/eduardo.garcia/blog/-/blogs/new-ways-of-customization-with-application-display-templates-part-i-

https://www.liferay.com/pt/web/eduardo.garcia/blog/-/blogs/new-ways-of-customization-with-application-display-templates-part-ii-

 
See you soon.
Blogs
What is the liferay capability of getting recently modified documents and recently accessed documents ?
We have a portlet who do that or you can use the asset publisher and order that documents from modified date.
That's exactly what i've been trying to do on my own months ago without success.
I'm very pleased to see this solution documented.

Thanks Paulo!
Thanks for the great post. It is very helpful. My question is, if the structure is really complicated, won't parsing the XML have performance issues? Is this the best way to accomplish getting attribute values for elements in the structure?
Hello, very useful documentation.Though, I'm trying to get the Read More behaviour to work. From your example it's not working.Can you help ? Regards
Thanks for the article. Please you mentioned choosing a JournalArticle based on a specific structure, how do you go about doing that?
Thanks!
Nice article! Thanks!
dont rules emoticon

I need extract values from the Structure journalArticle.
this sentence
#set( $document = $saxReaderUtil.read($journalArticle.getContent()) )
Don't works.

$document is null. but $journalArticle.getContent(), it's not null. whats the problem?
How would you go about getting the full content of a journal article? Like in "full content" display template.
Thanks, great article. emoticon
The only thing I missed there is proper way of creating links.
Here is the code I found on liferay pages, hopefully useful for someone on this place:
#set( $link = $assetPublisherHelper.getAssetViewURL($renderRequest, $renderResponse, $curEntry) )
#if ("$assetLinkBehavior" != "showFullContent")
#set( $link = $renderer.getURLViewInContext($renderRequest, $renderResponse, $link) )
#end
Thanks a lot... this was very useful for me...
Just one change instead of curEntry, it worked with $entry
(as used in above main code)
Great post and very useful, but..I have 2 questions.

If you added the value for 2 different languages it's always getting the value for 1 language. How to make this language dependent?

How to add the label of the field (in the right language)?
Hi,

#foreach($dynamicElement in $rootElement.elements())
#if($validator.equals($fieldName, $dynamicElement.attributeValue("name")))
#foreach($dynamicContent in $dynamicElement.elements())
#if($dynamicContent.attributeValue("language-id") ==$locale)
<a href="$viewURL">
#set($contentText = $dynamicContent.getText())
<p>$contentText</p>
</a>
#end <!-- if language -->
#end <!-- forEach dynamicContent -->
#end <!-- if fieldName -->
#end <!-- forEach dynamicContent -->

I hope you find it useful,
To solve the problem with the redirection that occurs in version 6.2 (LPS-46830) you should replace the redirect parameter with the variable $currentURL. For example:

## solution to the incidence of LPS-46830 backurl
# set ($ replaceURL = $ urlViewContext.substring ($ urlViewContext.indexOf ("redirect ="), $ urlViewContext.length ()))
# set ($ newUrl = "redirect =" + $ currentURL)
# set ($ urlViewContext = $ urlViewContext.replace ($ replaceURL, newUrl $))
## solution to the incidence of LPS-46830 backurl
Great, Thank You
I have a question :
- How can We access a field of type "Link to Page".
$dynamicElement.element("dynamic-content").getText() does"nt work and it display the reference to element not the value

Thanks
Hi all.
If you want to access to full content, try this
<#assign assetRenderer = entry.getAssetRenderer() />
<#assign docXml = saxReaderUtil.read(assetRenderer.getArticle().getContent()) />
<#assign rootElement = docXml.getRootElement() />
<#assign availableLocales = rootElement.attribute("available-locales").getText() />
<#assign defaultLocale = rootElement.attribute("default-locale").getText() />
<#if (availableLocales?contains(locale)) >
<#assign fulllContent = docXml.valueOf("//static-content[@language-id='" + locale + "']/text()") />
<#else>
<#assign fulllContent = docXml.valueOf("//static-content[@language-id='" + defaultLocale + "']/text()") />
</#if>

Hope it helps
Hi Víctor.
I tried use your solution to access "Full Content", but don't work for me. Would you help me solve my problem?

I tried create an ADT


<#assign liferay_ui = taglibLiferayHash["/WEB-INF/tld/liferay-ui.tld"] />

<div id="lista-sanfona" class="lista-sanfona">

<div class="lista-sanfona-inner">
<#list entries as entry>

<#assign assetRenderer = entry.getAssetRenderer() />

<#assign docXml = saxReaderUtil.read(assetRenderer.getArticle().getContent()) />

<#assign rootElement = docXml.getRootElement() />

<#assign availableLocales = rootElement.attribute("available-locales").getText() />

<#assign defaultLocale = rootElement.attribute("default-locale").getText() />


<#assign entryTitle = htmlUtil.escape(assetRenderer.getTitle(locale)) />

<#assign viewURL = assetPublisherHelper.getAssetViewURL(renderRequest, renderResponse, entry) />

<#assign dateFormat = "dd/MM/yyyy" />

<#if assetLinkBehavior != "showFullContent">
<#assign viewURL = assetRenderer.getURLViewInContext(renderRequest, renderResponse, viewURL) />
</#if>

<#if entry_index % 2 == 0>
<#assign classe_alt = "even" />
<#else>
<#assign classe_alt = "odd" />
</#if>

<div class="sanfona-item item-${entry_index} ${classe_alt}">

<h4 class="header toggler-header-collapsed">
<span class="titulo">${entryTitle}</span>
</h4>
<div class="content toggler-content-collapsed">
<#if (availableLocales?contains(locale)) >
<#assign fullContent = docXml.valueOf("//static-content[@language-id='" + locale + "']/text()") />
${fullContent}
<#else>
<#assign fullContent = docXml.valueOf("//static-content[@language-id='" + defaultLocale + "']/text()") />
${fullContent}
</#if>
</div>

<span class="lfr-meta-actions asset-actions">
<@getEditIcon />
</span>
<div class="clearfix"></div>
</div>
</#list>
</div>
</div>
<#macro getEditIcon>
<#if assetRenderer.hasEditPermission(themeDisplay.getPermissionChecker())>
<#assign redirectURL = renderResponse.createRenderURL() />

${redirectURL.setParameter("struts_action", "/asset_publisher/add_asset_redirect")}
${redirectURL.setWindowState("pop_up")}

<#assign editPortletURL = assetRenderer.getURLEdit(renderRequest, renderResponse, windowStateFactory.getWindowState("pop_up"), redirectURL)!"" />

<#if validator.isNotNull(editPortletURL)>
<#assign title = languageUtil.format(locale, "edit-x", entryTitle) />

<@liferay_ui["icon"]
image="edit"
message=title
url="javascript:Liferay.Util.openWindow({dialog: {width: 960}, id:'" + renderResponse.getNamespace() + "editAsset', title: '" + title + "', uri:'" + htmlUtil.escapeURL(editPortletURL.toString()) + "'});"
/>
</#if>
</#if>
</#macro>
Great article.

I was using freemarker ADT and converting the code listed in the article from the velocity template. I started to get errors:

Expression dynamicElement.attributeValue("name") is undefined .....

The same code from the article cut and pasted into a template on the same site with the same page with the same content works without error. The reason I did this is I was getting other errors from other example code in freemarker which were producing similar errors.

I am using liferay 6.2 CE.

The code I have for freemarker is as follows: (I have extended it to handle the repeat values in my structure).
The extended version in velocity works as required. For now I am going to just convert my full templates to velocity to get around this problem.

Is there something I am missing on the code below?

<#if entries?has_content>

<#list entries as entry>
<#assign renderer = entry.getAssetRenderer()>
<#assign className = renderer.getClassName() >

<#if className == "com.liferay.portlet.journal.model.JournalArticle" >
<#assign journalArticle = renderer.getArticle() >
<#assign document = saxReaderUtil.read(journalArticle.getContent()) >
<#assign rootElement = document.getRootElement() >



<#list rootElement.elements() as dynamicElement >
<#if "rawLinks" == dynamicElement.attributeValue("name") >
<#assign subTitle1 = dynamicElement.element("dynamic-content").getText() >
first alternative -> ${subTitle1} <br />
</#if>
</#list>



<#assign xPathSelector = saxReaderUtil.createXPath("dynamic-element[@name='rawLinks']") >
<#list xPathSelector.selectNodes(rootElement) as subTitle2>
second alternative -> ${subTitle2.getStringValue()} <br />
</#list>
</#if>
</#list>
</#if>
Any chance updating this p[ost to refer to 7.0? Finding many changes in ADT, not just service locations due to modularizing, but XPathselectors as well. Would be great to have a single space of reference/example ADT's for DXP/7.0