Knowledge Base

Overview

Handlebars allows you to create Content Layouts and Page Layouts with placeholders and simple logic that will be filled in with data at Publish time, making it easier to manage and manipulate content in ways that are not possible using standard T4 Tags without needing extensive coding expertise or familiarity with the complexities of Programmable Layouts.

One of the key features of Handlebars is its simplicity. With a syntax that closely resembles regular HTML, users can quickly grasp the basics and start building more "dynamic" Content types and Page layouts.

Handlebars uses double curly braces {{}} to denote placeholders, which are then replaced with actual data when the layout is Published – much like the t4 tags you may be familiar with today.

The Terminalfour implementation of Handlebars comes with an extensive toolkit of "Helpers" to allow you to add logic to your layouts in a way that just isn't possible with t4 tags.

From Terminalfour 8.3.19 you can make use of Handlebars by selecting it as a processor type on your Page Layouts and Content Layouts:

A screenshot of the General Tab when creating a Content Layout showing Handlebars is now an option in the Processor dropdown

Before we dive in...

8.3.19 is our initial release of Handlebars and we'll be extending the functionality and improving the experience over the next several releases of the platform.

Before we explore how Handlebars works it's important to mention a few items up front to help you make the best use of the feature.

Mixing t4 tags and Handlebars expressions

When setting the Content layout processor to Handlebars Content you can no longer use "T4 tags" in the layout. Instead you must use the Handlebars Helpers outlined in the rest of this guide.

Types of expressions

There are three main types of expressions you will be working with in your Handlebars code:

//Standard Expression:

{{exampleHelper "foo" example="bar"}}

//Block Expression:

{{#exampleHelper}}
  <p>Some stuff</p>
{{/exampleHelper}}

//Subexpression:

{{exampleHelper (otherHelper)}}

Subexpressions

A subexpression is an expression inside another expression. It’s used when you want to call a helper or a calculation inside another expression. Subexpressions are wrapped in parentheses.

For example, if you wanted to use the publish Helper to output the Title element of a Content item it might look like this:

{{publish element="Title"}}

But if you wanted to make use of the built in upper Helper to output that value in uppercase you'd need to pass in the publish Helper into the upper Helper as a subexpression like this:

{{upper (publish element="Title")}}Notice that by using subexpressions you can combine different Helpers, thereby creating more powerful Handlebars expressions. You will look at several examples of doing this throughout this training, remember that when using subexpressions you will need to use parentheses in your syntax. 

Types of parameters

You can pass as many unnamed or named parameters to your Custom Helpers as you'd like and in any order. You will see several different scenarios in the exercises.

//Unnamed Parameter:

{{exampleHelper "foo"}}

//Named Parameter:

{{exampleHelper example="bar"}}

"Double stashing" and "Triple stashing"

With some limited exceptions, Handlebars HTML-escapes values returned by an expression using two curly-braces (e.g. {{expression}}).

This means certain special characters will be converted to their HTML equivalents. For example, if a user entered a value of < then a "double-stashed" handlebars expression would convert that to <.

If you don't want Handlebars to escape a value, use the "triple-stash", {{{

Double-stash example

{{publish element="Title"}}If a user enters the value <h2>Test</h2> into a Title element then this expression would output <h2>Test</h2> (the value is HTML-escaped)

Triple-stash example

{{{publish element="Title"}}}If a user enters the value <h2>Test</h2> into a Title element then this expression would output <h2>Test</h2> (the value is not HTML-escaped)

Error handling

Like with standard T4 tag based layouts and programmable layouts it's possible to configure things incorrectly and get undesired or unexpected results.

Preview & Direct Edit

When an error is encountered with a Handlebars layout during Preview or Direct Edit we output an inline error message on the page to users.

This error is in the form of a table and displays the following info:

  1. The Section ID where the error is occurring
  2. The asset language
  3. The Content ID (if applicable)
  4. The error message
  5. The content layout name (if applicable)
  6. The layout code (if applicable)

This should give you enough information to find out where the problem exists so you can debug.

Example:

An example of a Handlebars error in previewPublish

When a Publish happens and an error is encountered we could have done one of two things:

  1. We could ignore the error, and continue the Publish
  2. We could stop the publish to prevent breaking the live site

Both have their pros and cons and ultimately we'll need to hear from you, our users, about how you're using Handlebars to determine the best long term solution.

For now, we've taken the safest and least destructive option which is to fail the publish. This means if you accidentally introduce a breaking error in your most commonly used Content Type or Page Layout we'll prevent that error from getting pushed to your live site and ultimately breaking a significant portion of your web pages.

When the publish fails it does so with very clear messaging in the logs explaining the reason for the failure.

Example

An. excerpt of the logs showing publish errors

 

Overview of JavaScript ES6 Syntax

String Literals

Prior to ES6 concatenation using the + operator was used.

var foo = "bar";
var oldWay = "The value of foo is: " + foo;

ES6 introduced Template literals which use backticks and allow interpolation with ${expression}.

var foo = "bar";
var newWay = `The value of foo is ${foo}`;

Spread Syntax

Spread syntax lets you break up elements of arrays or properties of an object. The code below shows using spread syntax to copy an object.

var obj = {
  foo: "bar"
}

var newObj = {
  ...obj,
  baz: "quz"
}

console.log (newObj);

output:
{
  foo: "bar",
  baz: "quz"
}

let and const

let and const both introduced in ES6 are two block-scoped ways of declaring variables.

let foo = "bar";
foo = "baz"; //This will work

const quz = "bar";
quz = "baz"; //This will error

Some other concepts

Type Coercion

The !! operator is a quick way to coerce any value into a Boolean (true or false).

let isValid = '';
typeOf isValid; // 'string'
typeOf !! isValid; // 'boolean'

Ternary Operator

The ternary operator ? is a shorthand for an if else statement.

let isValid = true;
let foo = isValid ? 'true' : 'false'

Before you start

Be sure you have the following added to your training site:

1. Duplicate the "T206: Training Site Example (Duplicate Me)" into your Site Structure
2. Duplicate the "T206 Training Site Example (Duplicate Me)" Page Layout
3. Create a "T206 Training Site Example" Channel
4. Duplicate the "T206 Training Site Example: General (Duplicate Me)" Content Type
5. Enable your "T206 Training Site Example: General" Content Type into your Home branch section.

Where to create Custom Helpers

There is no dedicated UI to manage Custom Helpers. Custom Helpers are added by adding an instance of a new System Content Type to a hidden section.

You will need to find the ID of the section you need to edit, to do so run the following SQL command:

select * from config_option where config_key = 'handlebars.helpersSectionId'
In the training instance you will be using throughout this course, the Section Id needed to access the Custom Helpers hidden Section is: 8303. You may want to crate a bookmark to this section to access easily as you are completing the exercises in the course.

1. Hello, World!

  • Let's start with a Hello World example to learn how to create Custom Helpers in Terminalfour. In this example, you will learn where and how Custom Helpers are created and how to use context and options to output named and unnamed parameters.

Exercise 1.1: creating the "your_nameHelloWorld" Custom Helper

  1. Click on any section within your site structure.
  2. Modify the URL for your section by changing the section Id in the URL to '8303' and hitting enter. This will take you to the hidden section in the training instance where you can add your Custom Helpers.
  3. Click on the Add Content button to create a new Custom Helper.
  4. Name: Give your Custom Helper a name, "your_nameHelloWorld". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  5. Function Code: Enter your Custom Helper code into this field as specified below. 

All Handlebars Custom Helpers start out as a function that has two arguments: context and options.
function (context, options) {

  return `Hello, World!`;

}

helloWorld Code

  1. Click Save changes to save the changes to your Custom Helper.

Exercise 1.2: updating the "text/html" content layout

  1. Go to Assets > Content Types.
  2. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  3. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  4. Add a Handlebars expression that uses the "your_nameHelloWorld" Custom Helper that you created in the previous exercise. To do this use the name of the newly created Custom Helper ("your_nameHelloWorld") inside the curly braces as below:

<p>{{your_nameHelloWorld}}</p>

  1. Click Save changes to save your Content Type.

Let's add some sample content using the "T206 [Your Name] Training Site Example: General" Content Type.

1. On the Site Structure screen, click on the name of your "Home" section.
2. The General information about this section screen appears, select the Content types tab.
3. Using the Filter feature, locate your Content Type.
4. Select the radio button to enable your content type for either the branch or section:
    a. Enabled (branch): the Content Type can be used in this section as well as all its sub-sections.
    b. Enabled (section): the Content Type can be used in this section only.
5. Click Save changes to confirm your selection. You can now use the "T206 [Your Name] Training Site Example: General" Content Type to add content to your section.
6. Add one content item to your "Home" section of your website. Give the Name element a value such as your own name and leave all the other elements blank.

  1. Update your preview to check the result.

Exercise 1.3: adding an unnamed parameter 

  1. Go to Assets > Content Types.
  2. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  3. The Content layouts tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" default content layout to edit it.
  4. Update the existing code by adding an unnamed parameter like so (Replace the [Your Name] placeholder with your actual name):

<p>{{your_nameHelloWorld [Your Name]}}</p> 

You can also pass an unnamed parameter using the publish Helper rather than hard coding in a value like so:

<p>{{your_nameHelloWorld (publish element="Name")}}</p>

Here a subexpression is created that passes the "Name" element of the Content Type as an unnamed parameter.

<p>{{your_nameHelloWorld (publish element="Name")}}</p>

  1. Click Save changes to save the changes to your Content Type.
  2. Go to your "your_nameHelloWorld" Custom Helper.

The context parameter will contain the first unnamed parameter passed to a Custom Helper. Additional unnamed parameters can be retrieved using options.You will now make use of the context parameter to access the unnamed parameter inside of your Custom Helper code.

  1. Update your Custom Helper code as per below:

function (context, options) {

  return `Hello ${context}!`;

}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Update your preview to check the result. 

Exercise 1.4: adding exception handling to "your_nameHelloWorld"

You should always consider adding exception handling to your code, let's look at a couple of methods you can use to better capture potential errors in your code when using context and options.

Let's revisit the "your_nameHelloWorld" Custom Helper and add code to check whether the context parameter is set. Currently, if we use the "your_nameHelloWorld" Custom Helper and forget to pass an unnamed parameter we might get an unexpected result.

  1. Go to your "your_nameHelloWorld" Custom Helper and update the code as per below:

function (context, options) {

  //create a variable called type to store whether context is referring to the global object
  const type = Object.prototype.toString.call(context);

  //return the String only if the global object is not being returned by context
  if (type !== '[object global]' && type !== '[object Window]') {
    return `Hello ${context}!`;
  }

}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content layouts tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" default content layout to edit it.
  5. Update the existing code by removing the unnamed parameter from the Handlebars expression as per below:

{{your_nameHelloWorld}}

  1. Update your preview to check the result.

Exercise 1.5: adding a named parameter

  1. Go to Assets > Content Types.
  2. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  3. The Content layouts tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" default content layout to edit it.
  4. Update the existing code by adding a Handlebars expression that uses the "your_nameGreetings" Custom Helper that you will create in the next exercise with a named parameter. To do this use the name of the Custom Helper ("your_nameGreetings") inside the curly braces and add the name parameter like so (Replace the [First Name] and [Last Name] placeholders with your name and last name respectively):

<p>{{your_nameGreetings firstName="[First Name]" lastName="[Last Name]"}}</p>

  1. Click Save changes to save the changes to your Content Type.
  2. Update your preview to check the result. 

Notice the error being displayed to the page. The error is showing you that the Custom Helper fails because it does not exist when previewing.

Exercise 1.6: creating the "your_nameGreetings" Custom Helper 

Let's create a new Custom Helper to use named parameters.

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameGreetings". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Enter your Custom Helper code into this field as specified below. 

To access named parameters you will use the options parameter and call the the hash method. You will then pass the name of your parameter to the hash method.

function (context, options) {

  return `Greetings, ${options.hash('firstName')} ${options.hash('lastName')}!`;

}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Update your preview to check the result.

Exercise 1.7: adding exception handling to "your_nameGreetings"

Now, let's also add exception handling to the "your_nameGreetings" Custom Helper. In this instance you are using the options parameter instead.

The hash method of the options parameter has a second parameter that is the default output so if the first parameter is null for some reason, the second parameter will be output instead.

  1. Go to your "your_nameGreetings" Custom Helper and update the code as per below:

function (context, options) {

  return `Greetings, ${options.hash('firstName', 'First Name')} ${options.hash('lastName', 'Last Name')}!`;

}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content layouts tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" default content layout to edit it.
  5. Update the existing code by removing the "firstName" and "lastName" named parameters from the Handlebars expression as per below:

<p>{{your_nameGreetings}}</p>

  1. Update your preview to check the result.

2. Escaping JSON

  • In this exercise, you will create a Custom Helper, "your_nameEscapeJson" that will allow you to escape text to ensure it's safe to use as JSON output.

Exercise 2.1: creating the "your_nameEscapeJson" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameEscapeJson". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODO below to create your Custom Helper: 
    1. Return the JSON escaped string using the stringify method (HINT: Use the context parameter).

function (context, options) {

  //1. TODO: Return the JSON escaped string using the stringify method (HINT: Use the context parameter).
  return JSON.stringify();

}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Add a Handlebars expression that uses the "your_nameEscapeJson" Custom Helper. Make sure you pass the "Description" element as an unnamed parameter using the publish Helper.
  6. Let's also look at an additional way of adding exception handling to your layouts. In this case, let's add an ifSet Helper to output the JSON text only if the Description element has been previously populated. This will avoid situations where an empty string (context) is passed to the Custom Helper.

Adding an ifSet Helper in the content layout provides you with a very useful and practical method of exception handling. Rather than having to handle an empty or null context within the Custom Helper code you can do it in the content layout instead.

{{#ifSet element="Description"}}
  <p>{{your_nameEscapeJson (publish element="Description")}}</p>
{{/ifSet}}

  1. Click Save changes to save the changes to your content layout.
  2. Go into the Home section of your site structure and update your existing content by adding text in the "Description" element.

Sample text to be converted to JSON:
"name": "[First Name]",
"last_name": "[Last Name]",
"academic_program": "[Computer Science]"

  1. Click Save changes to save your content.
  2. Update your preview to check the result.

3. Trim it to win it!

  • In this exercise, you will create a Custom Helper, "your_nameTrim". The "your_nameTrim" Custom Helper can be used to remove whitespace before and after text.

Exercise 3.1: creating the "your_nameTrim" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameTrim". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODO below to create your Custom Helper:
    1. Use the JavaScript trim function to remove whitespace and return the string. 

function (context, options) {

  //1. TODO: Use the JavaScript trim function to remove whitespace and return the string.
  return 

}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Add Handlebars expressions that use the "your_nameTrim" Custom Helper:

<pre>{{your_nameTrim "     string to trim     "}}</pre>
<pre>{{publish element="Name"}}</pre>
<pre>{{your_nameTrim (publish element="Name")}}</pre>

  1. Click Save changes to save your Content Type.
  2. Update your preview to check the result.

4. String search basics

  • In this exercise, you will create your first block expression Custom Helper.
  • Let's explore what is available within options and build a Custom Helper that uses some of its available methods.
  • The "your_nameStringContains" Custom Helper you will build next checks whether a String is contained within a second String.
Method What it does
options.params Returns a List <Object> of all positional parameters
options.param(int) Gets a specific positional parameter by index
options.hash(String) Gets a named (hash-style) param
options.fn() Gets the template block (for block content)
options.inverse() Gets the {{else}} block
options.tagType Returns VAR, TRIPLE_VAR, SUB_EXPRESSION or SECTION

The template block retrieved by the fn method refers to the content provided within the opening and closing of a block expression. The else block refers to any content after an optional else clause and that is retrieved by the inverse method.

Exercise 4.1: creating the "your_nameStringContains" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameStringContains". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODOs below to create your Custom Helper: 
    1. Get the string from context and store it into the "mainString" variable.
    2. Get the string to check using the param method.
    3. Return "this" using the fn method.
    4. Return "this" using the inverse method.

function (context, options) {

  //1. TODO: Get the string from context and store it in the "mainString" variable.
  const mainString

  //2. TODO: Get the string to check using the param method.
  const stringToCheck

  const result = mainString.indexOf(stringToCheck);
  
  if (result !== -1) {
    //3. TODO: Return "this" using the fn method. 
    return 
  }
  //4. TODO: Return "this" using the inverse method.  
  return 
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Add a Handlebars block expression that uses the "your_nameStringContains" Custom Helper:

{{#your_nameStringContains "string-to-check" "Terminalfour"}}
  <p>Yes, the first string contains the second string!</p>
{{else}}
  <p>No. The first string does not contain the second string!</p>
{{/your_nameStringContains}}

  1. Click Save changes to save your Content Type.
  2. Update your preview to check the result.

Adding different behaviors to the "your_nameStringContains" Custom Helper

The fulltext Helper for example takes on completely different behavior if it is used as a block expression vs a standard expression.
As a standard expression, the fulltext Helper will output a link to a fulltext page. In the code below, the fulltext Helper would generate a link to a fulltext page using the "Headline" element of a Content Type.

{{{fulltext linkText=(publish element="Headline")}}}In contrast, as a block expression, the fulltext Helper will let you know if the current page is a fulltext page or not.

{{#fulltext}}
  <p>This is a fulltext page.</p>
{{else}}
  <p>This is not a fulltext page.</p>
{{/fulltext}}

Learning to use the "tagType" variable

Let's learn how we can add different behaviors to our Custom Helpers. The key is to use the "options.tagType" variable which will return a string letting you know the type of expression:

function (context, options) {
  return options.tagType;
}
The returned value in the above code example will be one of the following:

Value (options.tagType) Expression
VAR Standard expression
TRIPLE_VAR Standard expression that is "triple stashed"
SUB_EXPRESSION Subexpression
SECTION Block expression

You can use these values to provide different behaviors depending on the type of expression being used for your Custom Helpers.

Let's build an example where we are adding an additional behavior to your "your_nameStringContains" Custom Helper if it is used as a standard expression. When used as a standard expression, "your_nameStringContains" should return the length of the String passed in.

Exercise 4.2: updating the "your_nameStringContains" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the "your_nameStringContains" Custom Helper button to update it. 
  3. Function Code: Complete the TODOs below to update your Custom Helper: 
    1. Add an if statement that will execute the code for block expressions.
    2. Add an else condition that will handle all other expressions.
    3. Return the length of the passed in string if it's a standard expression.

function (context, options) {

  //1. TODO: Add an if statement that will execute the code for block expressions. 
  const mainString = context;
  const stringToCheck = options.param(0);
  const result = mainString.indexOf(stringToCheck);

  if (result !== -1) {
    return options.fn(this);
  }

  return options.inverse(this);

  //2. TODO: Add an else condition that will handle all other expressions.

  //3. TODO: Return the length of the passed in string if it's a standard expression. 
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Update your preview to check the result.

Exercise 4.3: updating the "text/html" content layout

  1. Go to Assets > Content Types.
  2. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  3. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  4. Add a Handlebars expression that uses the "your_nameStringContains" Custom Helper as a standard expression. To do this use the Custom Helper ("your_nameStringContains") inside the curly braces as below:

<p>{{your_nameStringContains "count how many characters are in this string"}}</p>

  1. Click Save changes to save your Content Type.
  2. Update your preview to check the result.

Exercise 4.4: creating the "your_nameSelectedContains" Custom Helper

  • The "your_nameSelectedContains" Custom Helper that you will create next can be used to evaluate if a user has selected a given string from a list in a Content Type.
  • In this exercise, you will get additional practice using the fn and inverse methods, as well as, using the hash method to retrieve a named parameter passed to the custom Helper.
  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameSelectedContains". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODOs below to create your Custom Helper: 
    1. Add an if condition that returns the message "No string passed to compare" when the 'string_to_check' parameter is empty.
    2. Get the 'string_to_check' using the hash method.
    3. Return "this" using the fn method.
    4. Return "this" using the inverse method.

function (context, options) {
  //1. TODO: Add an if condition that returns the message "No string passed to compare" when the 'string_to_check' parameter is empty.
  if() {
    return `<div style="border: 1px solid #300;background-color:#fdd;color:#300;padding:1rem;font-size:1rem;border-radius:10px">No string passed to compare</div>`;
  }
 
  //2. TODO: Get the 'string_to_check' using the hash method.
  const testString = 
 
  let id = 'name';
  if (options.hash('field_to_check') === 'name' || options.hash('field_to_check') === 'value') {
    id = options.hash('field_to_check');
  }
   
  let result = false;
  for (let i = 0; i < context.length; i++) {
    if(context[i].get(id) === testString) {
      result = true;
      break;
    }
  }
  if(result) {
    //3. TODO: Return "this" using the fn method. 
    return
  }
  //4. TODO: Return "this" using the inverse method.
  return
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Add a Handlebars block expression that uses the "your_nameSelectedContains" Custom Helper:

{{#ifSet element="Academic Program"}}
  {{#your_nameSelectedContains (selected element="Academic Program") string_to_check="Education" }}
    <p>You must consult with an advisor first to select this academic program.</p>
  {{else}}
    <p>Your selection is now complete and has been forwarded to the registration office!</p>
  {{/your_nameSelectedContains}}
{{/ifSet}}

  1. Click Save changes to save your Content Type.
  2. Go into the Home section of your site structure and update your existing content by adding an academic program in the "Academic Program" list element.
  3. Click Save changes to save your content.
  4. Update your preview to check the result.

5. It's about time: epoch edition

  • In this exercise, you will get to create the "your_nameEpoch" Custom Helper. It will be used to return the number of seconds since January 1, 1970, given a specific date. In essence, it will take in a date and then perform calculations to return back the number of seconds since January 1, 1970 to that date.

Please be advised that date calculations in your Custom Helpers are based on the publish date not on when the page is being viewed.

Exercise 5.1: creating the "your_nameEpoch" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameEpoch". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODOs below to create your Custom Helper:
    1. Get the date from context and store it into the "dateString" variable. 
    2. Pass the date to the floor method to convert it to seconds. (HINT: Use the getTime() method).

function (context, options) {

  //1. TODO: Get the date from context and store it into the "dateString" variable.  
  const dateString =

  const date = new Date(dateString);

  //2. TODO: Pass the date to the floor method to convert it to seconds. (HINT: Use the getTime() method).

  const epochTime = Math.floor( /1000); 

  return epochTime.toString();

}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Add a Handlebars expression that uses the "your_nameEpoch" Custom Helper. Make sure to pass the "date" element as an unnamed parameter using the publish Helper.

Consider making the "Date" element required within your Content Type as an additional way of adding exception handling and ensuring you are not passing an empty value to your Custom Helper.

<p>{{your_nameEpoch (dateFormat (dateElement element="Date") "YYYY/MM/dd hh:mma")}}</p>

  1. Click Save changes to save your Content Type.
  2. Go into the Home section of your site structure and update your existing content by adding a date in the "Date" element.
  3. Click Save changes to save your content.
  4. Update your preview to check the result.

6. From start to finish: handling date ranges

  • The "your_nameDateRange" Custom Helper can be used to check whether a date is between a range of dates and then output a corresponding message.
  • Let's use the "your_nameDateRange" Custom Helper to check whether the current date falls between a holiday start date and a holiday end date and then output a corresponding message to the page. 

Please be advised that date calculations in your Custom Helpers are based on the publish date not on when the page is being viewed.

Exercise 6.1: creating the "your_nameDateRange" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameDateRange". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODOs below to create your Custom Helper:
    1. Get the holiday start date from context.
    2. Get the holiday end date using the param method.
    3. Return "Holiday opening hours".
    4. Return "Regular opening hours".

function (context, options) {

  //1. TODO: Get the holiday start date from context.
  const holidayStart =
 
  //2. TODO: Get the holiday end date using the param method.
  const holidayEnd =
 
  let todaysDate = new Date();

  // Remove "IST" or any extra time zone abbreviation
  const cleanedHolidayStart = holidayStart.replace(/ [A-Z]{3,4}$/, '');
  const cleanedHolidayEnd = holidayEnd.replace(/ [A-Z]{3,4}$/, '');

  const d1 = new Date(cleanedHolidayStart);
  const day1 = String(d1.getDate());
  const month1 = String(d1.getMonth() + 1);
  const year1 = d1.getFullYear();

  const d2 = new Date(cleanedHolidayEnd);
  const day2 = String(d2.getDate());
  const month2 = String(d2.getMonth() + 1);
  const year2 = d2.getFullYear();

  let from = new Date(year1, month1-1, day1);
  let to = new Date(year2, month2-1, day2);

  let isBetweenDates = (todaysDate > from && todaysDate < to);

  if (isBetweenDates) {
    //3. TODO: Return "Holiday opening hours".
    return 
  } 
  //4. TODO: Return "Regular opening hours".
  return
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Click into the </> Content layout code tab and add a Handlebars expression that uses the "your_nameDateRange" Custom Helper:

Consider making the "Start Date" and "End Date" elements required within your Content Type as an additional way of adding exception handling and ensuring you are not passing an empty value to your Custom Helper.

<p>{{your_nameDateRange (publish element="Start Date") (publish element="End Date")}}</p>

  1. Click Save changes to save your Content Type.
  2. Go into the Home section of your site structure and update your existing content by adding a dates to the "Start Date" and and "End Date" elements.
  3. Click Save changes to save your content.
  4. Update your preview to check the result.

7. Math

  • The "your_nameMath" Custom Helper can be used to perform some simple mathematical operations on some inputs.
  • It can be used to perform any of the following operations:

    • Addition - using + 
    • Subtraction - using - 
    • Multiplication - using * 
    • Division - using / 
    • Modulus - using % 

Exercise 7.1: creating the "your_nameMath" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameMath". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODOs below to create your Custom Helper: 
    1. Extract the operator using the param method.
    2. Extract the right hand value using the param method. (HINT: Check how "lvalue" is retrieved from context).
    3. Add the operations. You should add: "+", "-", "*", "/", and "%". If the operator is blank for some reason make it default to addition.

function (context, options) {

  //Convert context to a number
  const lvalue = context % 1 === 0 ? parseInt(context): parseFloat(context);

  //1. TODO: Extract the operator using the param method. 
  const operator = 

  //2. TODO: Extract the right hand value using the param method. (HINT: Check how "lvalue" is retrieved from context).
  const rvalue = 

  // Perform the appropriate operation based on the provided operator

  let result = '';
    
    //3. TODO: Add the operations. You should add: "+", "-", "*", "/", and "%". If the operator is blank for some reason make it default to addition. 

  return result;
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Add Handlebars expressions that use the "your_nameMath" Custom Helper:

<p>{{your_nameMath 5 '+' 2}}</p>
<p>{{your_nameMath 5 '-' 2}}</p>
<p>{{your_nameMath 5 '*' 2}}</p>
<p>{{your_nameMath 5 '/' 2}}</p>
<p>{{your_nameMath 5 '%' 2}}</p>

  1. Click Save changes to save your Content Type.
  2. Update your preview to check the result.

8. Embed your YouTube video and amp up your content!

  • In this exercise, you will create a Custom Helper, "your_nameVideoEmbed" that will allow you to output video.
  • The "your_nameVideoEmbed" Helper can be used to convert a youtube or vimeo into an iframe effectively. This allows users to paste YouTube or Vimeo links into a plain text field in various formats and have them be converted effectively into an iframe to be output on the page.

Exercise 8.1: creating the "your_nameVideoEmbed" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameVideoEmbed". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Copy/Paste the code below to create your Custom Helper: 

function (context, options) {
  const media = {};
 
  if(context.match('https?://(www.)?youtube|youtu.be')) {
    const ytrx = `^.*(?:(?:youtu\.?be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?"']*).*`;
    const ytr = context.match(ytrx);
    media.type = 'youtube';
    media.id = ytr[1];
  }
  else if (context.match('https?://(player.)?(www.)?vimeo.com')) {
    const vrx = `^.*(?:(?:vimeo\.?com\/|v\/|vi\/|v\/u\/\w\/|video\/)|(?:(?:video)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?"'\/]*).*`;
    const vr = context.match(vrx);
    let vimeo_id = vr ? vr[2] || vr[1] : null;
    media.type = 'vimeo';
    media.id = vimeo_id;
  }
 
  if (media.type === 'youtube') {
    return `<iframe class="embed-responsive-item" width="560" height="315" src="https://www.youtube.com/embed/${media.id}" frameborder="0" allowfullscreen></iframe>`;
  }
  if (media.type === 'vimeo') {
    return `<iframe class="embed-responsive-item" src="https://player.vimeo.com/video/${media.id}?color=f36788" width="560" height="315" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>`;
  }
   
  // error handling
  if (publishConfig.isPreview ()) {
    return `<div style="background: #fdd; color: #411; padding: 1rem; font-size: 1rem; border: 1px solid #411;border-radius: .25rem;margin-bottom: 1rem"><strong>Preview error:</strong> Invalid video link added</div>`;
  }
  else {
    return `<script>console.warn('Video embed cannot be created because an invalid video URL was supplied')</script>`;
  }
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Add a Handlebars expression that uses the "your_nameVideoEmbed" Custom Helper. Make sure you pass the "Video" element as an unnamed parameter.

The your_nameVideoEmbed Custom Helper will generate an iframe <iframe> tag, notice that you will need to triple-stash your expression so the html being generated gets parsed correctly and the video displays within an embedded iframe to your page.

{{#ifSet element="Video"}}
  {{{your_nameVideoEmbed (publish element="Video")}}}
{{/ifSet}}

  1. Click Save changes to save the changes to your content layout.
  2. Go into the Home section of your site structure and update your existing content by adding a Video URL of your choice in the "Video" element.
  3. Click Save changes to save your content.
  4. Update your preview to check the result.

Exercise 8.2: updating the "your_nameVideoEmbed" Custom Helper

Let's enhance the "your_nameVideoEmbed" Custom Helper to handle an additional case where rather than a video URL is entered a YouTube video Id is entered instead.

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click into "your_nameVideoEmbed Custom Helper".
  3. Function Code: Complete the TODOs below to update your Custom Helper: 
    1. Add an else condition to handle the case where a YouTube video Id is entered rather than a video URL.
    2. Set the media.type.
    3. Set the media.id.

function (context, options) {
  const media = {};
 
  if(context.match('https?://(www.)?youtube|youtu.be')) {
    const ytrx = `^.*(?:(?:youtu\.?be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?"']*).*`;
    const ytr = context.match(ytrx);
    media.type = 'youtube';
    media.id = ytr[1];
  }
  else if (context.match('https?://(player.)?(www.)?vimeo.com')) {
    const vrx = `^.*(?:(?:vimeo\.?com\/|v\/|vi\/|v\/u\/\w\/|video\/)|(?:(?:video)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?"'\/]*).*`;
    const vr = context.match(vrx);
    let vimeo_id = vr ? vr[2] || vr[1] : null;
    media.type = 'vimeo';
    media.id = vimeo_id;
  }
  //1. TODO: Add an else condition to handle the case where a YouTube video Id is entered rather than a video URL.
 
    //2. TODO: Set the media.type.
    //3. TODO: Set the media.id.

  if (media.type === 'youtube') {
    return `<iframe class="embed-responsive-item" width="560" height="315" src="https://www.youtube.com/embed/${media.id}" frameborder="0" allowfullscreen></iframe>`;
  }
  if (media.type === 'vimeo') {
    return `<iframe class="embed-responsive-item" src="https://player.vimeo.com/video/${media.id}?color=f36788" width="560" height="315" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>`;
  }
   
  // error handling
  if (publishConfig.isPreview ()) {
    return `<div style="background: #fdd; color: #411; padding: 1rem; font-size: 1rem; border: 1px solid #411;border-radius: .25rem;margin-bottom: 1rem"><strong>Preview error:</strong> Invalid video link added</div>`;
  }
  else {
    return `<script>console.warn('Video embed cannot be created because an invalid video URL was supplied')</script>`;
  }
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go into the Home section of your site structure and update your existing content modifying the "Video" element by passing in a YouTube video Id. In the URL below you would enter: "2v6wxLVfbvg" into the "Video" element.

https://www.youtube.com/watch?v=2v6wxLVfbvg

  1. Click Save changes to save your content.
  2. Update your preview to check the result.

9. The text that adapts: responsive style

  • In this exercise, you will create the "your_nameResponsiveText" Custom Helper which can be used to return responsive markup for a text element. It requires two parameters, the text and the length of the string at which point the element should be hidden on small screens.

Exercise 9.1: creating the "your_nameResponsiveText" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameResponsiveText". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODOs below to create your Custom Helper: 
    1. Get the element using the context parameter and store it in the "elementStr" variable.
    2. Get the length of the string using the param method.
    3. Return the string as is by storing it in the "responsiveString" variable.

function (context, options) {
  
  //1. TODO: Get the element using the context parameter and store it in the "elementStr" variable.  
  const elementStr = 

  //2. TODO: Get the length of the string using the param method.
  const lengthOfString =

  //Some hard coded default values which could be overwritten using elements in your content type)  
  let defaults = {
    breaker: '...',
    smallClass: 'show-for-small-only',
    largeClass: 'show-for-large-up'
 };

   let separator,
       shortenedString,
       remainingCharacters,
       responsiveString;

  // if the string is shorter that the specified length, just return the element as is. 
  if (elementStr.length < lengthOfString) {
       //3. TODO: Return the string as is by storing it in the "responsiveString" variable.
       
   } else {
     shortenedString = elementStr.substr(0,lengthOfString);
     remainingCharacters = elementStr.substr(lengthOfString);
     separator = (defaults.breaker === undefined) ? '' : `<span class="${defaults.smallClass}">${defaults.breaker}</span>`;
 
      responsiveString = `${shortenedString} ${separator} <span class="${defaults.largeClass}"> ${remainingCharacters}</span>`;
  }
  return responsiveString;
};

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Click into the </> Content layout code tab and add a Handlebars expression that uses the "your_nameResponsiveText" Custom Helper:

{{#ifSet element="Heading"}}
  <h2>{{{your_nameResponsiveText (publish element="Heading") "50"}}}</h2>
{{/ifSet}}

  1. Click Save changes to save your Content Type.
  2. Go into the Home section of your site structure and update your existing content by adding a heading in the "Heading" element.
  3. Click Save changes to save your content.
  4. Update your preview to check the result.

10. The IPublish API

The IPublishAPI allows you to interact with the Terminalfour platform from within Custom Helpers. It is a public facing, customer centric, read only API.

All core built-in Helpers provided by Terminalfour are implemented using the IPublishAPI. Any new Helpers that are created going forward will also be backed up by the IPublishAPI.

How to use the IPublish API

IPublishAPI Documentation:


https://docs.terminalfour.com/publishapi/8.4.0/com/terminalfour/publish/api/IPublishAPI.html

To access the IPublish API, you can make use of the apis variable. See examples below:

apis

//Examples:
apis.getPage(); //gets the current Page

apis.getSection(); //gets the current section

apis.getPage().getPageURL(); //gets the current page URL

apis.getSection().getId(); //gets the current section Id

Let's create some Handlebars expressions to practice using the IPublish API:

Exercise 10.1: get the current channel name

Write an expression to retrieve the current channel's name.

Exercise 10.2: get the expiry date of the currently publishing content item

Write an expression to retrieve the expiry date of the currently publishing content item.

Exercise 10.3: find if an element is set

Write an expression that returns whether an element is set. Assume the name of the element is: "Heading".

Exercise 10.4: find if the current page is a fulltext page

Write an expression that returns whether the current page is a fulltext page.

Exercise 10.5: get the level of the current section

Write an expression that returns the level of the current section.

Exercise 10.6: get the content type id of the content in the current section

Write an expression to return the content type id of the content in the current section.

Exercise 10.7: get all the elements of a content type

Write an expression that returns all the elements of a content type provided the id of the content type is: 10.

Exercise 10.8: get the content of the current section in a list format

Write an expression that returns the content of the current section as a list.

Exercise 10.9: get the entries of a list element

Write an expression that returns the entries of a list if the content id is: 10 and the name of the list element is: "My List".

Exercise 10.10: get the media layout of a media element in the current section

Write an expression that returns the media layout of a media element provided the name of the element is: "My Image".

Exercise 10.11: get the value of a text element

Write an expression that returns the value of a text element named: "JSON Text".

11. Generating reports from webpage content

  • In the following exercise, you will create a report for the content of a specified section. You will create a Custom Helper called "your_nameContentReportBySection" and use it to return a list of the content items in a specific section determined by the section Id passed to the Custom Helper.
  • This Custom Helper makes use of the "DataTables" JavaScript table library which will have to be available within your page layout.

Exercise 11.1: setting up the "DataTables" Library

The Content Report is generated using the "DataTables" Library. In order to use this library, you will need to insert the below code into the header of your page layout.

  1. Go to Assets > Page Layouts.
  2. Click your page layout name to edit it. (Use the Filter tool to search).  Open the </> Header code tab.
  3. Copy/paste the code below into the header of your page layout:

<script type="text/javascript" src="https://cdn.datatables.net/1.10.12/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" type="text/css" media="all" href="https://cdn.datatables.net/1.10.12/css/jquery.dataTables.min.css" />
<script type="text/javascript">
  $(document).ready(function(){
    $('table').DataTable();
  });
</script>

  1. Click Save changes to save the changes to your page layout.

Exercise 11.2: creating the "your_nameContentReportBySection" Custom Helper

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the Add Content button to create a new Custom Helper.
  3. Name: Give your Custom Helper a name, "your_nameContentReportBySection". (There should be no spaces in the name field, use camel case as a convention for your Custom Helpers names).
  4. Function Code: Complete the TODOs below to create your Custom Helper: 
    1. Get the content as a list by providing the section Id.
    2. Get the content Id.
    3. Get the Content Item.
    4. Get the Content Type Id.
    5. Get the expiry date.
    6. Get the last modified date.
    7. Get the publish date.
    8. Get the version.
    9. Build the report row by inserting the variables.

function(context, options) {
  
  let contentReport = `<table class="table-striped">
                       <thead>
                         <tr>
                           <th>Content Id</th>
                           <th>Content Type Id</th>
                           <th>Expiry Date</th>
                           <th>Last Modified Date</th>
                           <th>Publish Date</th>
                           <th>Version</th>
                          </tr>
                        </thead>`;
 
  //1. TODO: Get the content as a list by providing the section Id.
  let contentList =

  for (let i = 0; i < contentList.length; i++) {

    //2. TODO: Get the content Id.
    let contentId

    //3. TODO: Get the Content Item.
    let contentItem =

    //4. TODO: Get the Content Type Id.
    let contentTypeId

    //5. TODO: Get the expiry date.
    let expiryDate

    //6. TODO: Get the last modified date.
    let lastModified

    //7. TODO: Get the publish date.
    let publishDate

    //8. TODO: Get the version.
    let version

    //9. TODO: Build the report row by inserting the variables.
    contentReport += `<tr>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                      </tr>`;

  }                  

  contentReport += `</table>`;

  return contentReport;
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Go to Assets > Content Types.
  3. Click your Content Type name to edit it. (Use the Filter tool to search).  Open the Content layouts tab.
  4. The Content Layout tab is an area to add Content Layout(s) for your Content Type. Click into the "text/html" content layout to edit it.
  5. Add a Handlebars expression that uses the "your_nameContentReportBySection" Custom Helper. Make sure you pass a valid Content Id from your site structure.
  6. Complete the TODO below:
    1. Write a Handlebars expession that uses the "your_nameContentReportBySection" Custom Helper and passes in a Content Id from your site structure. Replace [####] with a valid Id from your site structure.

{{{your_nameContentReportBySection [####]}}}

  1. Click Save changes to save your Content Type.
  2. Update your preview to check the result.

Exercise 11.3: adding a content column to the report

For this next part of the exercise, you will add an additional column labeled "Content" to the report. For each content item in the section you will now add the content inside of the "Content" column.

To do this, you will make use of the IPublish API and iterate through the individual elements contained within each content item. As you iterate through the elements, you should output the name of the element followed by the value of the element in the following format:

Academic Program: 
Date:
Description:
End Date:
Heading:
Main body:
Name:
Start Date:
Video:

Notice the elements are displayed in alphabetical order.

  1. Access the hidden section where your Custom Helpers are added via the bookmark you created at the start of training.
  2. Click on the "your_nameContentReportBySection" Custom Helper to edit it.
  3. Function Code: Complete the TODOs below to update your Custom Helper: 
    1. Add a new column labeled "Content".
    2. Get the elements.
    3. Convert the keys of the elements to an array and store them in keys.
    4. Convert the values of the elements to an array and store them in values.
    5. Check if the element is a date element.
    6. Get the value of the Date element and store it in content. Use the following format: <p>Key: Value</p>.
    7. Check if the element is a text element.
    8. Get the value of the text element and store it in content. Use the following format: <p>Key: Value</p>.
    9. Check if the element is a list element.
    10. Get the list entries.
    11. Iterate through the list entries and store the selected value in the "selected" variable.
    12. Get the value of the list element and store it in content. Use the following format: <p>Key: Value</p>.
    13. Add a new column to the report row to output the content. 

function (context, options) {

  //10. TODO: Add a new column labeled "Content".
  let contentReport = `<table class="table-striped">
                       <thead>
                         <tr>
                           <th>Content Id</th>
                           <th>Content Type Id</th>
                           <th>Expiry Date</th>
                           <th>Last Modified Date</th>
                           <th>Publish Date</th>
                           <th>Version</th>
                          </tr>
                        </thead>`;
 
  //1. TODO: Get the content as a list by providing the section Id.
  let contentList = apis.getSection().listContent(context);

  for (let i = 0; i < contentList.length; i++) {

    //2. TODO: Get the content Id.
    let contentId = contentList[i].getId();

    //3. TODO: Get the Content Item.
    let contentItem = apis.getContent().get(contentId);

    //4. TODO: Get the Content Type Id.
    let contentTypeId = contentItem.getContentTypeId();

    //5. TODO: Get the expiry date.
    let expiryDate = contentItem.getExpiryDate();

    //6. TODO: Get the last modified date.
    let lastModified = contentItem.getLastModified();

    //7. TODO: Get the publish date.
    let publishDate = contentItem.getPublishDate();

    //8. TODO: Get the version.
    let version = contentItem.getVersion();
    
    //11. TODO: Get the elements.
    let elements = 

    //12. TODO: Convert the keys of the elements to an array and store them in keys.
    const keys = 

    //13. TODO: Convert the values of the elements to an array and store them in values.
    const values = 

    //Variable used to store the content.
    let content = '';

    //Iterate through the elements.
    for (let i = 0; i < keys.length; i++) {

      //14. TODO: Check if the element is a date element.
      if () {
        //15. TODO: Get the value of the Date element and store it in content. Use the following format: <p>Key: Value</p>. 
        content += 
      }

      //16. TODO: Check if the element is a text element.
      else if () {
        //17. TODO: Get the value of the text element and store it in content. Use the following format: <p>Key: Value</p>. 
        content += 
      }

      //18. TODO: Check if the element is a list element.
      else if () {
        //19. TODO: Get the list entries.
        let entries = 
        //Variable used to store the selected item in the list.
        let selected;
        //20. TODO: Iterate through the list entries and store the selected value in the "selected" variable.
        for (let i = 0; i < entries.length; i++) {
          if () {

          }
        }
        //21. TODO: Get the value of the list element and store it in content. Use the following format: <p>Key: Value</p>. 
        content += 
      }
   
    }
    //22. TODO: Add a new column to the report row to output the content.
    //9. TODO: Build the report row by inserting the variables.
    contentReport += `<tr>
                        <td>${contentId}</td>
                        <td>${contentTypeId}</td>
                        <td>${expiryDate}</td>
                        <td>${lastModified}</td>
                        <td>${publishDate}</td>
                        <td>${version}</td>
                      </tr>`;

  }                  

  contentReport += `</table>`;

  return contentReport;
}

  1. Click Save changes to save the changes to your Custom Helper.
  2. Update your preview to check the result.