TerminalFour programmable layouts

Aaron Lott
Thursday 11 August 2016

TerminalFour programmable layouts

The following article assumes you are an existing power user or administrator of T4 and have access to their community extranet site. It gives an overview of what programmable layouts are and provides useful examples of how they can be used to greatly simplify the setup of T4.

For example, consider the need to have a different navigation object to display the list of sections within a particular level of the site structure. Without programmable layouts you would need to have a page layout for each navigation object that links to each level of the structure. With programmable layouts you can have just one page layout with server side JavaScript deciding which navigation object to use based on criteria such as the name of the section or level within the structure.

Overview

Programmable layouts allow the insertion of server side JavaScript. This can be done in three ways:

1. Set the content layout processor to use JavaScript content

When you set up the content layout you can use the JavaScript content option to run server side JavaScript. If you use this, you must use JavaScript to output all HTML elements on the page i.e. you can’t just put HTML tags in there.

Example screenshot of content type layout

2. Set the page layout to use the JavaScript page layout processor

Like the content layout processor, you can select the JavaScript Page layout processor. This will allow server side JavaScript to be run within the page layout. However, like the content type layout, all elements on the page will have to be output using JavaScript. If there is an error in the JavaScript then this has the potential to break your entire site!

Screenshot of example page layout informatoin3. Use the Section Meta Data Type to assign a system content type to the hierarchy

The following describes the method we use and provides the most flexibility.

a. Create a system content type and apply it to the hierarchy

This involves the following steps:

  1. Create a content type with just the default ‘name’ field. This can be called something like ‘Programmable layout’
  2. Get the ID of the content type created (this is visible in the content asset screen)
  3. Assign this content type as a system type  by running UPDATE template SET template_type=30 WHERE id=<TEMPLATE ID> on the database
  4. This system type content type needs to be assigned to sections by going to:
    • Tools > Configuration
    • Select Handlers tab
    • Click configure beside the Hierarchy Handler
    • Select the Section Meta Data Type – select the content type you have just created and click OK to save.

Note that v8 the ‘Tools > Configuration’ options in not available from the menu. You can get it by manually altering the URL to have SiteManager?ctfn=config

After this has been completed, the ‘Programmable layout’ content type is available throught the site structure for any sections that are added after this has been activated. If you need to retrospectively enable this across an existing structure then you will need to as TerminalFour client support for help.

The ‘Programmable layout’ content type is invisible to the user when adding content, but can be accessed by using the related content navigation object that can access the content type layouts (note that in T4 v7 you need to use the related content advanced navigation object).

See http://community.terminalfour.com/how-to/programmable-layouts—sectionmeta-description-approach/ for more details.

b. Set up content layouts

Within the ‘Programmable layout’ content type you need to create a content layout for each type of logic you want to use. The following screenshot shows how we have created a content layout for logic that controls features such as breadcrumbs, footer, header, menus and other scripts. Within each of these content layouts we use the JavaScript content layout processor (as per step 1 above) to add code.

Example screenshot of content layouts

c. Create related content navigation objects

The final step is to create related content navigation objects that will use the content layout defined in step b above. For example, to use the text/breadcrumb content layout we have a navigation object that looks for any content in the section that has text/breadcrumbs as the alternate content layout.

Excample screenshot of navigation objectOnce the navigation object has been created it can be added to a page layout or the content layout of another content type

Programmable layouts – the practical guide

The following examples provide the JavaScript code that needs to be added to the content layout for the ‘Programmable layout’ content type. The most basic blocks you need are these two:

Utilities, for accessing T4 tags and stuff:

importClass(com.terminalfour.publish.utils.BrokerUtils);

Execute and write T4 tags:

document.write(BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="navigation" name="Name of your awesome T4 tag" id="7" />',''));

It’s also good practice to wrap your code with a try-catch:

try {
  .... code stuff here ...
} catch (e) {
 document.write(e);
}

How we’re actually using programmable layouts:

Changing the navigation menus based on the section level within the site structure:

importClass(com.terminalfour.publish.utils.BrokerUtils);
var t4Tag = '<t4 type="navigation" name="programmable layouts - get section name" id="26" />';
var sectionName = com.terminalfour.publish.utils.BrokerUtils.processT4Tags(dbStatement, publishCache, section, null, language, isPreview, t4Tag);
var homepageName = section.getName('en');

if (homepageName == 'University') {
 document.write(BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="navigation" name="Navigation - top level" id="10"/>',''));
 document.write(BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="navigation" name="Navigation - sub sites" id="9" />',''));
}
else if(sectionName == 'About' || sectionName == 'Business services' || sectionName == 'Community facilities') {
 document.write(BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="navigation" name="Navigation - top level" id="10"/>',''));
 document.write(BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="navigation" name="Navigation - sub sites" id="9" />',''));
}
else {
 document.write(BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="navigation" name="Navigation - sub sites" id="9" />',''));
}
prog-layout-indv
What one of our programmable layouts actually looks like

Bringing in a particular script on a page/section where you need it:

importClass(com.terminalfour.publish.utils.BrokerUtils);

var sectionName = section.getName('en');

if(sectionName == 'Watch graduation live'){
 document.write('<script src="//releases.flowplayer.org/6.0.5/commercial/flowplayer.min.js"></script>');
}

 

Getting and building a gallery automatically based on the media library folders:

importPackage(java.lang);
importPackage(com.terminalfour.tag.parser);
importPackage(com.terminalfour.media);
importPackage(com.terminalfour.sitemanager);
importPackage(com.terminalfour.publish.utils);
importClass(com.terminalfour.publish.utils.BrokerUtils); 

var mediaManager = MediaManager.getManager();

try
{
var oStmt = dbStatement;
var oMM = MediaManager.getManager();
var oCH = new ContentHierarchy();
var oBU = new BrokerUtils(); 

var contentS1 = content.get('Media Library section ID');

var sid = contentS1.getValue();
var alltheMedia = oCH.getContent(oStmt,sid,'en');

var title = BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="content" name="Title" output="normal" modifiers="" />','');

var description = BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="content" name="Description" output="normal" modifiers="medialibrary,nav_sections" />','');

document.write('<div class="gallery"><h2 class="gallery__title">' + title + '</h2><div class="gallery__description">' + description + '</div><div class="gallery__images">');

for (var i = 0; i < alltheMedia.length; i++)
{ 
 var theMedia = oMM.get(oStmt,alltheMedia[i],"en");
 
 var theTag = new StringBuilder().append("<t4 type=\"media\" id=\"")
 .append(new Integer(theMedia.getID()))
 .append("\" formatter=\"image/*\" />").toString();
 
 var currentMediaObject = mediaManager.get(dbStatement.getConnection(), theMedia.getID(), language);
 var imageDescription = currentMediaObject.description; 
 
 sVarNew = oBU.processT4Tags(oStmt, publishCache, section, theMedia.getContent(), language, isPreview, theTag);
 
 document.write('<figure>' + sVarNew + '<figcaption>' + imageDescription + '</figcaption></figure>');
}

document.write('</div></div>');

} catch (e) {
 document.write(e);
}

 

Potential ideas

Content dependent layout

Give content editors the option to place content within a page layout from a drop-down within the content type.

http://community.terminalfour.com/how-to/programmable-layouts—content-dependant-layout/

importClass(com.terminalfour.publish.utils.BrokerUtils);
//this nav brings in the chosen option from the 'Choose Layout' content in the section
var pageLayout = BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, '<t4 type="navigation" id="186" />';

if(pageLayout == "Left Column") {
   document.write('<div class="small-12 medium-9 medium-push-3 columns">');
}
else if (pageLayout == "Right Column") {
   document.write('<div class="small-12 medium-8 columns">');
}
else {
   document.write('<div class="small-12 medium-6 large-6 columns">');
}

Current section information

Get information about the current section

http://community.terminalfour.com/how-to/programmable-layouts—current-section-information/

var channel = publishCache.getChannel(); //get the channel object

document.write('SEO Keyphrases: ' + section.getKeyPhrase('en') + '<br/>');
document.write('Current section name: ' + section.getName('en') + '<br/>');
document.write('Current section style id: ' + section.getStyle(channel) + '<br/>');
document.write('Current sectionLevel: ' + section.getLevel(channel) + '<br/>');

Parent/grandparent/ancestor information

For retrieving information on the parent section such as section ID, level, name and page layout

http://community.terminalfour.com/how-to/programmable-layouts—grandparentancestor-information/

http://community.terminalfour.com/how-to/programmable-layouts—parent-section-information/

var channel = publishCache.getChannel(); //get the channel object
var sectionParent = section.getParent(); //get the parent object
var sectionGrandparent = sectionParent.getParent();

if (sectionGrandparent) {
   document.write('grand parent ID: ' + sectionGrandparent.getID() + '<br/>');
   document.write('grand parent level: ' + sectionGrandparent.getLevel(channel) + '<br/>');
   document.write('grand parent name: ' + sectionGrandparent.getName('en') + '<br/>');
   document.write('grand parent style ID: ' + sectionGrandparent.getStyle(channel) + '<br/>');
}
else {
   document.write('no grandparent');
}

List element output

Change the output based on the value of a list element.

http://community.terminalfour.com/how-to/programmable-layouts—list-element-output/

var value = content.get('select'); //This is an element called 'select' in our content type - we're getting it's value as a variable we can test against

if(value.publish() == 'option A'){ //check if value equals to option A
   document.write('option A has been selected');
}

else if(value.publish() == 'option B'){//check if value equals to option B
   document.write('option B has been selected');
}

Default content

Output default meta data if it is missing

http://community.terminalfour.com/how-to/programmable-layouts—meta-data-and-default-content/

var value = section.getMetaInfoValue (16, 'en');
if(value) {
   document.write('<meta name="description" content="'+ value +'">');
}
else {
   document.write('<meta name="description" content="This is my default meta description for when my users forget to add it">');
}

Alternate formatter of current content

Get another content layout of the same content. For example, a user can choose from a drop down the version of the content they want to show e.g. user could choose which image to use.

Alternatively, on a particular page layout you could only use a certain content layout.

http://community.terminalfour.com/how-to/programmable-layouts—alternate-formatter-of-current-content/

importPackage(com.terminalfour.template);
importPackage(com.terminalfour.sitemanager);
var tid = content.getTemplateID();
var tempManager = com.terminalfour.template.TemplateManager.getManager();
var formatter ='text/top';
var format = tempManager.getFormat(dbStatement,tid,formatter); //bring back an object of this template
var formatString = format.getFormatting(); //render the content on that object
document.write(com.terminalfour.publish.utils.BrokerUtils.processT4Tags(dbStatement, publishCache, section, content, language, isPreview, formatString));

Finally

See http://community.terminalfour.com/how-to/-/programmable-layouts/ and http://community.terminalfour.com/how-to/introducing-programmable-layouts/ for more details. There is also information on the forum with further examples and discussion.

 

Related topics

Share this story


3 thoughts on "TerminalFour programmable layouts"

  • Phil Wilson
    Tuesday 27 September 2016, 2.13pm

    Interesting stuff. Do you unit test your JavaScript? How do you test the code before shipping to production?

    Reply
    • Aaron Lott
      Aaron Lott
      Wednesday 28 September 2016, 11.06am

      Hey Phil, We don't unit test in the strict sense of the term with the server side Javascript because the input is going through T4 we feel we can rely on it more. Our process is more informal, basically varying input in T4 content type fields to make sure they're working as expected, but nothing written down. And it's worth mentioning that we've not yet moved to using T4v8 on a production server yet, still on the dev server. I would like to implement a more rigid process of testing because when the Javascript has an error it's not pretty for the pages in preview (and some functionality won't work of course). A note on the dev server, we'll definitely ensure code works on there before we go ahead and deploy on live. Thanks for your mention of unit testing though, as that would be wise to have a more rigid and formalised testing we'll go through before releasing the code into the wild on the production server! Best, Aaron

      Reply
      • Phil Wilson
        Wednesday 28 September 2016, 10.58pm

        Thanks Aaron. It sounds similar to the approach we developed when writing the equivalent for OpenCms, the platform we are currently migrating away from. In the end, small discrepancies would creep in as we copy/pasted code between dev and live and the maintenance cost of not being able to test our layouts or automate deployment of this kind of customisation (eg via CI/CD tools using our standard version control system) was one of the smaller reasons we moved away from a monolithic platform.

        Reply

Leave a reply

By using this form you agree with the storage and handling of your data by this website.