Knowledge Base

Programmable Layouts:
Pull Content from Selected Section

Last Modified:
30 Mar 2022
User Level:
Power User

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 htmlBeforeLayout = 'text/before';

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.