The Glorious Simplicity of Structure Inheritance

The Glorious Simplicity of Structure Inheritance

This field (highlighted yellow) on a structure editing screen is what I am talking about.

When I first saw the words Parent Structure there, the following thoughts came to mind, in increasing order of coolness

  1. All fields in the parent structure are inherited by the child structure

  2. The idea of polymorphic content, i.e. content of one type (i.e. structure) Y that is a child of type X, is also of type X. This is in essence the IS-A relationship in object oriented programming.

  3. Asset Publisher would respect above polymorphic behavior so I can simply query for content of the parent type and get back all content tied to that structure as well as all content tied to child structures.

  4. The idea that a child structure would automatically be serviced by the CMS template that serves its parent.

If you guessed that all of that is true, then you would be… wrong. The only point that holds true is the first: a structure simply inherits the fields of its parent structure (if one is specified).

That’s it! Done!

Now, this sort of obviates the need for the rest of this post. But if you’re like me, you want to try stuff out and push the limits a bit to see how stringent things really are, and what the actual payoff is, in practice, of using parent structures. So that is what follows.

Important: There is no IS-A relationship between a child structure and its parent. These are not classes - they are simply XML definitions complying with Liferay’s DDM structure schema. The child structure takes all fields that its parent structure offers, and then cuts the cord in all its individualistic glory, refusing to be identified as an extension of its parent for practical purposes. The only evidence of the relationship is in that parent-structure association observed in the child structure definition. I’ll demonstrate this from an Asset Publisher perspective in a bit.

Let’s have a parent

Okay. Time for something concrete.

I have my classic Poem structure - graphic shown below.

I use the above structure for all my classical poem forms. But now, I have a burning requirement to have a separate structure for my ecphrastic poems.

Ecphrasis

noun  ek·phra·sis \ˈek-frə-səs\

a literary description of or commentary on a visual work of art

My new structure needs all the fields that my existing Poem structure already has. In addition, it has two fields:

  • Picture (to capture an image of the painting or photograph that inspired the poem)

  • Citation (text related to the picture such as name of work, name of artist, etc.)

One option would be to redefine all the fields from the Poem structure in our new structure, which is actually very simple and quick to do. Simply go to the Source view of the structure, select all, copy and paste into the Source view of the new structure. But that is not the point here. We want to follow the DRY principle as much as possible so that we have common fields in one place, thus avoiding a proliferation of repetitive data that really ought to be maintained in one place. Remember, templates are going to be working with the fields of a structure, and templates are stubborn little creatures that fuss about field names and such, so following a DRY approach can get to be quite important from a maintainability standpoint.

Anyway, no TA-DA moment here. That Parent Structure field that every structure offers helps us out. This is what the new structure looks like now.

 

Note how the parent structure is selected as Poem. And that the new structure, Ecphrastic, has only two fields: Picture and Citation. So, when you go to create content of type Ecphrastic, you see something like this:

 

New structure needs new template

A template can be associated with one and only one structure. So a new template is needed by content that wishes to use your new structure. Of course, you could go to the template used for the parent structure and select the structure in the template definition to be the new one you created, but then it would cease to service content associated with the parent structure.

You will need a new template for your new structure. This is not a problem. Simply replicate your old template and add in the presentation parts to show the new fields. You may also consider import the existing template if the new fields just need to be tagged on to the end or beginning. Do whatever makes sense.

So, here’s my CMS template to service my new Ecphrastic structure.

#parse ("$templatesPath/$selectMood.getData()")
<h3>$txtPoemTitle.getData()</h3>
<div class="poem-body">
$htmlVerse.getData()
</div>
<div style="clear:both"></div>
<div class="about-poem">
$htmlAbout.getData()
<br/>
<img alt="Picture" src="$imgPicture.getData()" />
<br/>
<i>$htmlCitation.getData()</i>
</div>
 

The newly added presentation is highlighted. Basically we show the picture right-aligned and the citation text right below it. This is how our content looks now when the template renders it.

Ugly, I know; but in a beautiful sort of way, yes?

Asset Publisher is just being Asset Publisher

If you wanted to configure an Asset Publisher to bring your content back, you would literally have to configure it so as to bring back content matching both parent and child structure types. As far as consumers of content are concerned, each structure is a first class structure in its own right. The structure inheritance is just cleverness to keep from repeating common fields.

Here’s a snapshot of what an Asset Publisher configuration might look like.

Now, if you wanted to display a mix of content of types Poem and Ecphrastic, you would have to work that into your Application Display Template. The ADT does not really care about the relationships between structures - you are working with content within an ADT - the actual asset. In an ADT, you just use XPATH syntax to find the fields you want using their corresponding names. If the field is found in the content, you retrieve it, else you get null.

Here is the ADT I wrote to show the results of the above configured Asset Publisher instance.

#set ($vocabularyLocalService = $serviceLocator.findService("com.liferay.portlet.asset.service.AssetVocabularyLocalService"))
#set ($vocabularies = $vocabularyLocalService.getGroupVocabularies($getterUtil.getLong($groupId)))
### we are interested in 2 specific vocabularies - Schools and StudyLevels
#foreach($voc in $vocabularies)
   #if ($voc.name == "poems")
       #set ($poemsVocabularyId = $voc.vocabularyId)
   #end
#end

#if (!$entries.isEmpty())
   <div>
  #foreach ($curEntry in $entries)
  #set( $picture = "")
  #set( $renderer = $curEntry.getAssetRenderer() )
  #set( $link = $renderer.getURLViewInContext($renderRequest, $renderResponse, ''))
  #set( $journalArticle = $renderer.getArticle() )
  #set ($articlePrimKey = $journalArticle.getResourcePrimKey())
  #set ($catLocalService = $serviceLocator.findService("com.liferay.portlet.asset.service.AssetCategoryLocalService"))
  #set ($catPropertyLocalService = $serviceLocator.findService("com.liferay.portlet.asset.service.AssetCategoryPropertyLocalService"))
  #set ($articleCats = $catLocalService.getCategories("com.liferay.portlet.journal.model.JournalArticle", $articlePrimKey))
  #set($viewURL = $assetPublisherHelper.getAssetViewURL($renderRequest, $renderResponse, $curEntry))
  #set( $document = $saxReaderUtil.read($journalArticle.getContent()) )
  #set( $rootElement = $document.getRootElement() ) 
  #set( $pictureSelector = $saxReaderUtil.createXPath("dynamic-element[@name='imgPicture']") )
  #set( $picture = $pictureSelector.selectSingleNode($rootElement).getStringValue() )
  #set ($colorCode = "")

  ###### get the poem categories #######
  #foreach($cat in $articleCats)
    #if($cat.vocabularyId == $poemsVocabularyId)
      #set ($colorCode = $catPropertyLocalService.getCategoryProperty($cat.categoryId, "color_code"))
    #end
  #end
###### Display the poem as a card #######
  <a href="$viewURL" style="color:white;">
  <div class="span4" style="background-color: #$colorCode.value; color:white; padding:10px; margin:10px; height:200px; border-radius:10px;">
  <span style="font-weight:bold;color:white;font-size:20px;">${curEntry.getTitle($locale)}</span>
  <hr>
  #if ($picture != "")
    <img src="$picture" align="right" width="120px" style="padding:5px; margin:5px;">
  #end
  ${curEntry.getSummary($locale)}
  </div>
  </a>
#end
</div> 
#end
 
The only snippet I added for my new ecphrastic content was this:
#if ($picture != "")
  <img src="$picture" align="right" width="120px" style="padding:5px; margin:5px;">
#end
 
And here is what the Asset Publisher portlet response looks like when that ADT renders. You can see how my Ecphrastic content displays with a thumbnail of the insipirational artwork right-aligned. 
 

Okay, so why glorious?

Ah, yes!

The Glorious Simplicity of Structure Inheritance.

Two reasons why I chose that as title for this post.

  1. I was thinking about The Curious Case Of Benjamin Button - may have had something to do with it.

  2. The simplicity is glorious because IMO we don’t want any more cleverness than the sharing of common fields to come out of this relationship between structures. Could you imagine the sort of madness that might ensue if an Asset Publisher, for instance, were to display content of a structure that simply extended another structure. What if the extension were only for reuse purposes and that there was no semantic relationship between the structures apart from field names (rare, but possible). The Asset Publisher already does a pretty good job with its querying mechanism giving us various criteria by which to bring back content (multiple structures, tags, categories, fields). We don’t need polymorphic madness injected into the mix.

That concludes it.

Blogs
Hello,

thanks a lot for this article, very intersting !

First question : How does
$catPropertyLocalService.getCategoryProperty($cat.categoryId, "color_code")
work in your case ?

Second question : Why don't you test if it is a Ecphrasic article before doing this :
#set( $pictureSelector = $saxReaderUtil.createXPath("dynamic-element[@name='imgPicture']") )
#set( $picture = $pictureSelector.selectSingleNode($rootElement).getStringValue() )
I guess you get a null to $pictureSelector if the node 'imgPicture' does not exist.
Answer to first question : https://web.liferay.com/fr/web/javeedchida/blog/-/blogs/fun-with-generic-content-templates
Sorry about that Javeed !
Yes, I suppose if I got the structure of the journalArticle, it would be more elegant than checking for the existence of a field from that structure. Do share if you have the API call handy to get that? I'll look for it as well. The only way I found was to get the ddSmStructureKey from a JournalArticleDisplay.

Thank you for taking the time to share.
Same here with DDMStructure, I do it this way :

<#assign structureKey = assetRenderer.getArticle().getStructureId()>
<#assign DDMStructureLocalService = serviceLocator.findService("com.liferay.portlet.dynamicdatamapping.service.DDMStructureLocalService")>
<#assign structure = DDMStructureLocalService.getStructure(entry.getGroupId(),entry.getClassNameId(),structureKey,true)>

${structure.getFieldLabel("fieldName", locale)}