TerminalFour programmable layouts
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.
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!
3. 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:
- Create a content type with just the default ‘name’ field. This can be called something like ‘Programmable layout’
- Get the ID of the content type created (this is visible in the content asset screen)
- Assign this content type as a system type by running UPDATE template SET template_type=30 WHERE id=<TEMPLATE ID> on the database
- 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.
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.
Once 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" />','')); }

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.
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.
Interesting stuff. Do you unit test your JavaScript? How do you test the code before shipping to production?
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
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.