Programmable Layouts:
Pull Content from Selected Section
Description
Sometimes you may want to be able to select content to pull into the page, and display that content using a specific layout. For example, selecting a Staff Profile, and displaying the person's first name, last name and photograph, linking through to further information. It is a bit like a Related Content navigation object, but
- allowing the section that is used to be chosen by the user who is creating the content, as opposed to it being set in the navigation object
- allowing the user who is creating the content to choose specific Content Items
This Programmable Layout can be used in a Content Layout to achieve this. It uses a section/content link to choose the content or section that contains the content and it alllows you to:
- use a section link to display all content in the linked section
- use a content link to display to link a specific content
- customise if you want to use only section link or content link or both
- add as many "containers" (section/content link elements) as you wish
- add a before and after HTML that wraps the containers, which can be standard layouts and optional
- add a different before and after HTML that wrap each different container, which can be standard layouts and optional.
- select content from the current section or a different section
- use either be a standard layout or another programmable layout to define the layout of the content being displayed
- easily specify which Content Layout needs to be used
- not display any errors if in the selected section does not contain any content with the specified Content Layout
- have optional containers
Instructions
1. Create a Content Type with the Containers
Create a Content Type called that contains the "containers" in Assets > Content Types. This implementation has a specific Content Type called "Column container" that is the container and has separate section / content link elements to find the content that needs to be displayed in each column. Any content can be selected and displayed, provided it has the specified content layout (in this case text/display).
Name | Type | Required | Max Size |
---|---|---|---|
Column 1 | Section/Content Link | Yes | n/a |
Column 2 | Section/Content Link | No | n/a |
Column 3 | Section/Content Link | No | n/a |
Save Changes.
2. Create the Content Layouts
text/before
This content layout will display what needs to appear before everything. It needs text/html (the programmable layout) in order to be displayed.
Name: text/before
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
text/after
This content layout will display what needs to appear after everything. It needs text/html (the programmable layout) to be displayed.
Name: text/after
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
text/colum-1-before
This content layout will display what needs to appear before the first column/container. It needs text/html (the programmable layout) to be displayed.
Name: text/column-1-before
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
text/colum-1-after
This content layout will display what needs to appear after the first column/container. It needs text/html (the programmable layout) to be displayed.
Name: text/column-1-after
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
text/colum-2-before
This content layout will display what needs to appear before the second column/container. It needs text/html (the programmable layout) to be displayed.
Name: text/column-2-before
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
text/colum-2-after
This content layout will display what needs to appear after the second column/container. It needs text/html (the programmable layout) to be displayed.
Name: text/column-2-after
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
text/colum-3-before
This content layout will display what needs to appear before the third column/container. It needs text/html (the programmable layout) to be displayed.
Name: text/column-3-before
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
text/colum-3-after
This content layout will display what needs to appear after the third column/container. It needs text/html (the programmable layout) to be displayed.
Name: text/column-3-after
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
text/html
This content layout will handle all the Programmable Layout code that creates the columns/containers that call all the different content and Content Layouts required.
Create a Content Layout called text/html (or the default layout that you use on your channel) and set the Content Layout Processor to JavaScript Content.
Copy & paste the code below into the layout:
try {
var FullListOutputImports = JavaImporter(
com.terminalfour.publish.utils.TreeTraversalUtils,
com.terminalfour.spring.ApplicationContextProvider,
com.terminalfour.content.IContentManager,
com.terminalfour.version.Version,
com.terminalfour.publish.utils.BrokerUtils,
java.io.StringWriter,
com.terminalfour.utils.T4StreamWriter,
com.terminalfour.publish.ContentPublisher,
com.terminalfour.navigation.ServerSideLinkManager,
com.terminalfour.sitemanager.cache.CachedContent,
com.terminalfour.sitemanager.cache.utils.CSHelper
);
with(FullListOutputImports) {
var columnElements = ['Column 1', 'Column 2', 'Column 3'];
var columnLayout = 'text/display';
var type = 'both';
var htmlBeforeLayout = 'text/before';
var htmlAfterLayout = 'text/after';
var htmlBeforeColumnLayout = ['text/column-1-before', 'text/column-2-before', 'text/column-3-before'];
var htmlAfterColumnLayout = ['text/column-1-after', 'text/column-2-after', 'text/column-3-after'];
function getCachedSectionFromId(sectionID) {
if (typeof sectionID === 'undefined') {
return section
} else if (section.getID() == sectionID) {
return section
}
sectionID = Number(sectionID)
if (sectionID == 0) {
throw 'Passed Incorrect Section ID to getCachedSectionFromId'
}
return TreeTraversalUtils.findSection(
publishCache.getChannel(),
section,
sectionID,
language
)
}
function getContentManager() {
return ApplicationContextProvider.getBean(
IContentManager
)
}
function getCachedContentFromId(contentID, contentVersion) {
if (typeof contentID === 'undefined' && typeof contentVersion === 'undefined') {
return content
} else if (Number(contentID) <= 0 && typeof contentVersion !== 'undefined' && content !== null) {
contentID = content.getID();
} else {
contentID = Number(contentID);
}
if (content === null && contentID === 0) {
throw 'Passed Incorrect Content ID to getContentFromId'
}
var contentManager = getContentManager();
if (typeof contentVersion !== 'undefined') {
return contentManager.get(contentID, language, Version(contentVersion))
} else {
var version;
if (isPreview) {
version = contentManager.get(contentID, language).version;
} else {
version = contentManager.getLastApprovedVersion(contentID, language);
}
return contentManager.get(contentID, language, version);
}
}
function getContentTypeFromId(contentID) {
if (typeof contentID === 'undefined' || (Number(contentID) <= 0 && content !== null)) {
return content.getContentTypeID()
}
contentID = Number(contentID);
if (content !== null && contentID === 0) {
throw 'Passed Incorrect Content ID to getContentTypeFromId'
}
var contentManager = getContentManager();
return contentManager.getContentType(contentID)
}
function processT4Tags(t4tag, contentID, sectionID, forMediaFile) {
var cachedContent = content || null;
var cachedSection = section;
if (typeof sectionID !== 'undefined' && sectionID !== null && Number(sectionID) > 0) {
cachedSection = getCachedSectionFromId(sectionID);
}
if (contentID === null && sectionID !== null) {
cachedContent = null;
} else if (typeof contentID !== 'undefined' && Number(contentID) > 0) {
cachedContent = getCachedContentFromId(contentID);
if (cachedContent == null) {
throw 'Could not get cachedContent';
}
}
if (cachedSection == null) {
throw 'Could not get cachedSection';
}
if (forMediaFile !== true) {
forMediaFile = false;
}
var renderedHtml = String(BrokerUtils.processT4Tags(dbStatement, publishCache, cachedSection, cachedContent, language, isPreview, t4tag));
if (forMediaFile) {
renderedHtml = renderedHtml.replace(/&/gi, '&');
}
return renderedHtml;
}
function getLayout(contentLayout, contentID, sectionID, displayError, forMediaFile) {
if (typeof contentLayout === 'undefined') {
throw 'getLayout: contentLayout is required for getLayout (' + contentLayout + ' of ' + content.getID() + ').';
}
if (contentLayout === '') {
return '';
}
if (forMediaFile !== true) {
forMediaFile = false;
}
var cachedSection = section;
var cachedContent = content;
if (typeof contentID !== 'undefined' && Number(contentID) > 0) {
if (typeof sectionID !== 'undefined' && Number(sectionID) > 0) {
cachedSection = getCachedSectionFromId(sectionID);
} else {
cachedSection = section;
}
cachedContent = getCachedContentFromId(contentID);
if (cachedSection == null || cachedContent == null) {
throw 'getLayout: Getting the custom content and section was not possible';
}
} else {
contentID = content.getID();
if (typeof sectionID !== 'undefined' && Number(sectionID) > 0) {
cachedSection = getCachedSectionFromId(sectionID);
} else {
sectionID = section.getID();
}
}
var tid, format, formatString, renderedHtml;
tid = getContentTypeFromId(contentID)
format = publishCache.getTemplateFormatting(dbStatement, tid, contentLayout)
formatString = format.getFormatting()
processorType = format.getProcessor().getProcessorType()
if (String(processorType) !== 't4tag') {
try {
var sw = new StringWriter()
var t4w = new T4StreamWriter(sw)
new ContentPublisher()
.write(
t4w,
dbStatement,
publishCache,
cachedSection,
cachedContent,
contentLayout,
isPreview
)
renderedHtml = sw.toString()
} catch (e) {
if (typeof displayError === 'undefined') {
displayError = true
}
if (displayError == true) {
throw '(getLayout' +
contentLayout +
'of ' +
contentID +
')' + e
} else {
renderedHtml = ''
}
}
} else {
renderedHtml = processT4Tags(formatString, contentID, sectionID)
}
if (forMediaFile) {
renderedHtml = renderedHtml.replace(/&/gi, '&');
}
return renderedHtml
}
function getLinkFromSectionLinkElement(elementName) {
if (typeof elementName === 'undefined' && elementName === '') {
throw 'elementName is required.'
}
var mySSLManager = ServerSideLinkManager.getManager()
sectionElement = content.get(elementName)
if (sectionElement.getValue() !== null && sectionElement.getValue() != '') {
return mySSLManager.getLink(
dbStatement.getConnection(),
sectionElement.getValue(),
section.getID(),
content.getID(),
language
)
} else {
return null
}
}
function getContentFromSection(excludeHidden, sectionID) {
cachedSection = section
if (typeof sectionID !== 'undefined' && Number(sectionID) > 0) {
cachedSection = getCachedSectionFromId(Number(sectionID))
}
if (typeof excludeHidden === 'undefined') {
excludeHidden = true
}
var mode = isPreview ?
CachedContent.CURRENT_IF_NO_DRAFT :
CachedContent.APPROVED
sectionContentAll = cachedSection.getContent(publishCache.getChannel(), language, mode, false)
if (excludeHidden === true) {
return CSHelper.extractCachedContent(
CSHelper.removeSpecialContent(
sectionContentAll
)
)
} else {
return CSHelper.extractCachedContent(
sectionContentAll
)
}
}
function getLayoutFromLink(columnElements, columnLayout, type, htmlBeforeLayout, htmlAfterLayout, htmlBeforeColumnLayout, htmlAfterColumnLayout) {
if (typeof columnElements === 'undefined') {
throw 'Please specify the element';
}
if (typeof columnLayout === 'undefined') {
columnLayout = 'text/html';
}
if (typeof type === 'undefined') {
type = 'both';
} else if (type != 'section' && type != 'content') {
type = 'both';
} else {
type = String(type)
}
if (typeof htmlBeforeLayout === 'undefined') {
htmlBeforeLayout = '';
}
if (typeof htmlAfterLayout === 'undefined') {
htmlAfterLayout = '';
}
if (typeof htmlBeforeColumnLayout !== 'object') {
htmlBeforeColumnLayout = [];
}
if (typeof htmlAfterColumnLayout !== 'object') {
htmlAfterColumnLayout = [];
}
var html = '';
if (htmlBeforeLayout) {
html += getLayout(htmlBeforeLayout);
}
for (var i = 0; i < columnElements.length; i++) {
var columnHtml = '';
var getElementLink = getLinkFromSectionLinkElement(columnElements[i]);
if (getElementLink !== null) {
if (getElementLink.toContentID > 0 && type != 'section') {
columnHtml += getLayout(columnLayout, getElementLink.toContentID, getElementLink.toSectionID);
} else if (getElementLink.toSectionID > 0 && type != 'content') {
var sectionContent = getContentFromSection(true, getElementLink.toSectionID);
for (var s = 0; s < sectionContent.length; s++) {
columnHtml += getLayout(columnLayout, sectionContent[s].getID(), getElementLink.toSectionID, false);
}
} else {
linkType = type === 'both' ? '' : type;
throw 'You didn\'t select a valid ' + linkType + 'link for ' + columnElements[i];
}
}
if (columnHtml != '') {
if (htmlBeforeColumnLayout[i]) {
columnHtml = getLayout(htmlBeforeColumnLayout[i]) + columnHtml;
}
if (htmlAfterColumnLayout[i]) {
columnHtml += getLayout(htmlAfterColumnLayout[i]);
}
}
html += columnHtml;
}
if (htmlAfterLayout) {
html += getLayout(htmlAfterLayout);
}
return html;
}
if (!(typeof htmlBeforeLayout === 'string' || htmlBeforeLayout instanceof String)) {
throw "htmlBeforeLayout must be a string"
}
if (!(typeof htmlAfterLayout === 'string' || htmlAfterLayout instanceof String)) {
throw "htmlAfterLayout must be a string"
}
if (!(typeof type === 'string' || type instanceof String)) {
throw "type must be a string"
}
if (typeof htmlBeforeColumnLayout !== 'undefined' && !Array.isArray(htmlBeforeColumnLayout)) {
throw "htmlBeforeColumnLayout must be an array"
}
if (typeof htmlBeforeColumnLayout === 'undefined') {
var htmlBeforeColumnLayout = [];
}
if (typeof htmlAfterColumnLayout !== 'undefined' && !Array.isArray(htmlAfterColumnLayout)) {
throw "htmlAfterColumnLayout must be an array"
}
if (typeof htmlAfterColumnLayout === 'undefined') {
var htmlAfterColumnLayout = [];
}
if (typeof columnElements !== 'undefined' && !Array.isArray(columnElements)) {
throw "columnElements must be an array"
}
if (typeof columnElements === 'undefined') {
var columnElements = [];
}
if (typeof columnLayout === 'undefined' || (typeof columnLayout !== 'undefined' && String(columnLayout) === '')) {
throw "columnLayout must be not empty"
}
if (typeof columnElements === 'undefined' || (typeof columnElements !== 'undefined' && String(columnElements) === '')) {
throw "columnElements must be not empty"
}
var html = getLayoutFromLink(columnElements, columnLayout, type, htmlBeforeLayout, htmlAfterLayout, htmlBeforeColumnLayout, htmlAfterColumnLayout)
document.write(html);
document.write('');
}
} catch (err) {
var contentID = typeof content !== 'undefined' ? ' content ID: ' + content.getID() : '';
var sectionID = typeof section !== 'undefined' ? ' section ID: ' + section.getID() : '';
var message = 'Programmable Layout Error: ' + err + ' occurred in ' + contentID + sectionID + ')';
var outputImports = JavaImporter(
org.apache.commons.lang.StringEscapeUtils,
java.lang.System
);
with(outputImports) {
if (isPreview) {
document.write(message);
} else {
document.write('');
}
System.out.println(message);
}
}
Depending on your requirements, the following lines of code can be customized:
The names of the elements used
var columnElements = ['Column 1', 'Column 2', 'Column 3'];
Enter the names of the elements that you created in your Column Content Content Type earlier. If there is only one element, this will be similar to:
var columnElements = ['Column 1'];
The names of Content Layout used to display the selected content
var columnLayout = 'text/display';
Enter the name of the Content Layout used to display the selected content.
Whether to allow the user to select Sections, Content or Either
var type = 'both';
Enter 'content' to only retrieve content if the user selects a Content item; enter 'section' to only retrieve content if a user selects a section; enter 'both' to retrieve content regardless of whether a user selects a section or content.
The Content Layout to output before any selected content
var htmlBeforeLayout = 'text/before';
Enter the name of the Content Layout that is added to this Content Type that outputs before all content.
The Content Layout to output after any selected content
var htmlAfterLayout = 'text/after';
Enter the name of the Content Layout that is added to this Content Type that outputs after all content.
The Content Layout to output before each container
var htmlBeforeColumnLayout = ['text/column-1-before', 'text/column-2-before', 'text/column-3-before'];
Enter the names of the layouts that are added to this Content Type that outputs before each container. If there is only one element this will look something like:
var htmlBeforeColumnLayout = ['text/column-1-before'];
The Content Layout to output after each container
var htmlAfterColumnLayout = ['text/column-1-after', 'text/column-2-after', 'text/column-3-after'];
Enter the names of the layouts that are added to this Content Type that outputs after each container.
3. Add Content Layouts to display the selected content
For each Content Type that is expected to display within the selected containers, the specified Content Layout needs to be added to define how the content is displayed. This Content Layout needs to be have the same name specified in programmable layout
var columnLayout = 'text/display';
For each Content Type, create a new Content Layout:
text/display
Name: text/display
Syntax Type: any
Content layout processor: T4 Standard Content
Content layout code: any
4. Create Content
Create Content using the "Column Container" Content Type, selecting either sections or content that is to be displayed by the containers. The selected sections and content must have the specified Content Layout in order to display.