Templating - Single Entry Templates

<<Previous Lesson       Next Lesson>>

Remember back in our planning chapter when we touched on the difference between Single Entry and Multi-entry content types. Our Content Model has two Channels that hold multiple entries (Services that holds the main Services landing page plus several sub-pages and Work that holds all of our portfolio entries). Still, the Homepage, About page, and Contact page are all examples of Single Entry content, and this is a better place to start actually building.

Before we dive in, though, I highly suggest taking the time to read through EE’s own documentation on the Template Language and while you’re there, note that there is very extensive documentation on EE Templates - make sure to utilize the documentation as you’re learning.

In This Article:

Step 1: Building Our Homepage

We already have a working template for our Homepage, our site/index template, but so far the only content is hard-coded, so we want to update that to pull in the fields we built out, assigned to the Homepage Channel, and then populated in earlier chapters.

Displaying ‘Native’ Content with the Channel Entries Tag

We retrieve content stored inside our Channels using EE’s most powerful tag: the Channel Entries Tag. At a high level, this tag can either output a single Entry or loop through multiple Entries in a Channel (or multiple Channels if needed) and outputs whatever content you want. The tag can accept certain parameters to further refine what Entries get included in the loop, but its most basic usage is very simple.

  1. Replace the hard-coded <h1> in site/index with the following, save, then refresh your browser:
    {exp:channel:entries}
      <h1>{title}</h1>
    {/exp:channel:entries}
    

    The result is that the tag loops through literally all of our Entries (from all Channels) and outputs everything between the opening and closing tags for each Entry in the loop, so in this case, we get one <h1> tag populated with EE’s default {title}single variable’ output to the page.


  2. Now try adding the channel and limit parameters to the opening exp:channel:entries tag:
    {exp:channel:entries channel="homepage" limit="1"}
      <h1>{title}</h1>
    {/exp:channel:entries}
    

    The result here is what we’re looking for - we just want to display one Entry from our Homepage Channel. Technically we don’t need the limit here because our Homepage Channel is limited to 1 Entry, but it’s a good habit to get into for Single Entry templates.


  3. Next, we want to output our Page Heading and Page Intro fields:
    • The Page Intro field is a Rich Text fieldtype, so the content it outputs already has HTML included in it, so we can literally just output the {page_intro} tag right after our <h1>:
      {exp:channel:entries channel="homepage" limit="1"}
        <h1>{title}</h1>
        {page_intro}
      {/exp:channel:entries}
      
    • The Page Heading, however, we had intended to use as a way for the user to specify the page’s main heading, but by default, we want to load EE’s default Title like we’re already doing and only override that if the Page Heading field is populated. The way we do this is by using Conditional Tags. It’s definitely worth reading the documentation here too, because there’s a ton of stuff you can do with Conditional Tags. I’ll try to incorporate as many examples as possible, but seriously, RTFM. The most basic conditional is the {if} tag:
      {if condition} data to show if condition is met {/if}
      

      which can also be used with {if:else} and {if:elseif}:

      {if condition_1}
        Condition 1 is true
      {if:elseif condition_2}
        Condition 1 is false, but Condition 2 is true
      {if:else}
        Neither condition is true
      {/if}
      

      So inside our <h1> tag, we will check to see if the Page Heading exists and if so, display that, otherwise, output the default EE Title:

      {exp:channel:entries channel="homepage" limit="1"}
        <h1>
          {if page_heading}
            {page_heading}
          {if:else}
            {title}
          {/if}
        </h1>
        {page_intro}
      {/exp:channel:entries}
      

      NOTE: We don’t need a conditional around our Page Intro field - because this field is Rich Text, we don’t need any surrounding HTML because the HTML is part of the field output based on what the user enters into the editor. If nothing is added to the Page Intro field, the {page_intro} variable tag will just not display anything at all.

  4. Finally, we want to pull in our Hero Carousel field. Grid and File Grid fields use Variable Pairs - remember, our Hero Carousel can have multiple rows and each of those can have multiple columns (or sub-fields). Just like the {exp:channel:entries} tag has an opening and closing tag to loop through all the Entries, Grid and File Grid fields have opening and closing tag and loop through all the rows of content.

    NOTE: Again, like the {exp:channel:entries} tag, opening tags for Grid and File Grid fields can also accept parameters to filter the output - it’s definitely worth keeping in mind the documentation for Fieldtypes as you’ll use this a lot.

    The opening and closing tags use the ‘short name’ of the main field (in our case hero_carousel), and then in each row, or iteration through the loop, we have access to the column (or sub-field) data via a tag that combines the main field short name and the column short name like this: {grid_field:column_field}.

    Note: From now on, I’ll deliberately omit large chunks of code and only focus on the parts I’m discussing to limit the length of code in the tutorial.

    Go ahead and add this to your template, above our <h1> but still inside the opening exp:channel:entries tag, then save and refresh your browser:

    <div class="carousel">
      {hero_carousel}
        <div div class="slide">
          <h2>{hero_carousel:heading}</h2>
          <p>{hero_carousel:text}</p>
          <p>{hero_carousel:file}</p>
        </div>
      {/hero_carousel}
    </div>
    

    There are a few things to note in the output here:

    • We’ll need more output to actually turn this into a carousel - for now, we’re just outputting each row from our Hero Carousel field in a new <div>
    • The first column in a File Grid is always has a ‘short name’ of {file} and is always ‘required,’ but the other two columns (Heading and Text) are not required in this case, so we want to add conditionals to handle that
    • Accessing any File Field (including the first column of a File Grid) has several ways you can interact with it. You can access it via a single variable like we’ve done above, which outputs the public path to that file (e.g., example.com/images/uploads/hero_banners/slide1.jpg). But you can also use a variable pair and access certain info from the file separately - check out the documentation on the File Fieldtype.
    • It’s possible that there might not be any rows at all in our Hero Carousel field, and if that’s the case, we don’t even want to output the surrounding <div> with class "carousel." Grid and File Grid fields have individual variables we can use to conditionally output these opening and closing tags, specifically the {count} (i.e., which row we’re in) and {total_rows} (i.e., how many total rows are being returned).

    So to clean this up and finish this off, try this instead of above:

    {hero_carousel}
      {if hero_carousel:count == 1}
        <div class="carousel">
      {/if}
          <div class="slide">
            {if hero_carousel:heading}<h2>{hero_carousel:heading}</h2>{/if}
            {if hero_carousel:text}<p>{hero_carousel:text}</p>{/if}
            <p>{hero_carousel:file wrap="image"}</p>
          </div>
      {if hero_carousel:count == hero_carousel:total_rows}
        </div>
      {/if}
    {/hero_carousel}
    

    Now we’re opening the ‘carousel’ <div> in the first row of the loop and closing it on the last, but for all rows in the loop, we output the ‘slide’ <div> and are checking if the Heading and Text is populated before we output it and we’ve used the handy wrap parameter on the File field that outputs our image!

The last step is here is to actually get this looking like a carousel - to do this, we’ll be using the Bootstrap Carousel with Captions. Just like before, we’ll still only output the starting HTML the first time through the loop and the closing HTML in the final time through, so the only part that gets output for each row is the ‘carousel-item’ <div>.

A couple more things to note:

So to finish this off, putting all of this together and updating our Hero Carousel code, our final code in our template should be like this:

{hero_carousel}
  {if hero_carousel:count == 1}
    <div id="carouselExampleCaptions" class="carousel slide" data-ride="carousel">
        <div class="carousel-inner">
  {/if}

          <div class="carousel-item {if hero_carousel:count == 1}active{/if}">
            {hero_carousel:file}
              <img src="{url:large}" class="d-block w-100" alt="{title}">
            {/hero_carousel:file}
            <div class="carousel-caption d-none d-md-block">
              {if hero_carousel:heading}<h2>{hero_carousel:heading}</h2>{/if}
            {if hero_carousel:text}<p>{hero_carousel:text}</p>{/if}
            </div>
          </div>

  {if hero_carousel:count == hero_carousel:total_rows}
      </div>
      <a class="carousel-control-prev" href="#carouselExampleCaptions" role="button" data-slide="prev">
        <span class="carousel-control-prev-icon" aria-hidden="true"></span>
        <span class="sr-only">Previous</span>
      </a>
      <a class="carousel-control-next" href="#carouselExampleCaptions" role="button" data-slide="next">
        <span class="carousel-control-next-icon" aria-hidden="true"></span>
        <span class="sr-only">Next</span>
      </a>
    </div>
  {/if}
{/hero_carousel}

NOTE: In this example, we’re using the variable pair for the ‘file’ column of our Hero Carousel and separately outputting the {url:large} variable (which is the URL to the large image Manipulation) and for the alt parameter we’re using the {title} variable, but note this is not the same {title} from our Channel Entry, it’s just one of the Template Tags available to the File fieldtype and outputs the Title metadata assigned to the file when it was first uploaded.

I’ve added some basic styling to the stylesheet already, so this is looking good, and we can move on!

Displaying ‘Visiting’ Content

Our Homepage isn’t done yet though - we also want to showcase our latest 3 portfolio projects, so we need to set up our Homepage template (site/index) to pull in this visiting content. To do this, we’ll use another Channel Entries loop, just with different parameters set. In this case, to pull in ‘visiting’ content, we will:

  1. Set the channel="work" parameter

  2. Limit the tag to only loop through 3 Entries with the limit="3" parameter

  3. Also include the dynamic="no" parameter. By default, the EE Channel Entries tag sets some parameters dynamically based on what’s in the URL, so specifying dynamic="no" tells the Channel Entries Tag to completely ignore the URL, which we want to do in this case. (Read more about the dynamic parameter here)

  4. Specify how the tag should order and sort the entries - by default, EE orders by date and sorts descending, so technically, these parameters aren’t necessary, but it’s worth adding them to highlight you could change it. See options for ordering and options for sorting.

  5. Go ahead and add links to the Work Entry detail pages even though we know the links won’t work yet. The URL structure we want is like this: example.com/work/sample-project where ‘sample-project’ is the URL Title of each Work Entry, so we will use EE’s Path Variables to do this.

<h2>Latest Projects</h2>
<div class="row">
  {exp:channel:entries channel="work" limit="3" dynamic="no" orderby="date" sort="desc"}
    <div class="col-4">
      <h3><a href="{path='work/{url_title}'}">{title}</a></h3>
      <p><em>{entry_date format="%F %d %Y"}</em></p>
      {work_images limit="1"}
        <img src="{work_images:file}" alt="{work_images:caption}" />
      {/work_images}
      {if client_name}
        <h3>{client_name}</h3>
      {/if}
      <p>{excerpt}</p>
      <p><a href="{path='work/{url_title}'}">Read More ></a></p>
    </div>
  {/exp:channel:entries}
</div>

There are a couple of things to note here too:

So now we have our Homepage not only outputting the page-specific content we added but also looping through the first 3 Work entries and displaying limited info for each one with links to the full ‘detail’ page, which we’ll be building out later.

Step 2: Building the Contact Page

Now that we’ve run through an example of a Single Entry template using the Channel Entries Tag in detail (excruciating detail?!), building out the next couple of templates won’t take anywhere near as long. Let’s start with the Contact page. We want our Contact page to live at example.com/contact, so we need to set up another Template Group, this time named "contact" and then the contact/index template for our contact page:

  1. In the CP, go to Developer > Templates, then use the blue New button next to the Template Groups heading in the left sidebar.

    NOTE: Clicking Create New Template here will add a new Template to our existing site group, which is not what we want.

  2. Enter the name "contact", leave everything else as the default and save
  3. In your text editor, open up the contact/index template and set the Template Layout we’re going use with {layout="layouts/_wrapper"} on the very first line (just like we did for our Homepage)
  4. Now lets re-use the first Channel Entries Tag from our Homepage template (ie site/index) with the exception of the {hero_carousel} variable pair - because the Contact Channel doesn’t have that field available to it. We also need to update the channel parameter to look for Entries in the Contact Channel:
    {exp:channel:entries channel="contact" limit="1"}
      <h1>{if page_heading}{page_heading}{if:else}{title}{/if}</h1>
      {page_intro}
    {/exp:channel:entries}
    
  5. Save the template and go to example.com/contact in your browser

That wasn’t so bad! But we’re not quite done yet - the Contact Channel has a few new fields we haven’t worked with in Templates yet, so lets add those:

  1. First we’ll output our Address field - this is a Grid, so we use the variable pair again. In this case, we know there can only ever be 1 row, so I’m putting the heading inside the loop - that way if there isn’t a row at all, the heading won’t show. We also need to be aware that we left the Line 2 column optional so we need to cater for that:
    {address}
      <h2>Address</h2>
      <p>
        {address:line_1}
        {if address:line_2}<br />{address:line_2}{/if}
        <br />{address:city}, {address:state} {address:zip_code}
      </p>
    {/address}
    
  2. Our Departments field is very similar, though this time we know there could be multiple rows, so we need to use a conditional so we only output the heading the first time through the loop. We made all the columns required, so this one should be simple:
    {departments}
      {if departments:count == 1}<h2>Departments</h2>{/if}
      <p>
        <strong>{departments:name}</strong>
        <a href="tel:+1-{departments:phone}">{departments:phone}</a>
        <a href="mailto:{departments:email}">{departments:email}</a>
      </p>
    {/departments}
    
  3. The Map Embed Code is a Textarea field intended to just paste in a Google Map <iframe>. We could theoretically wrap it with HTML to make it responsive, in which case we’d use a conditional to make sure it existed first, so if it didn’t, we wouldn’t have empty HTML tags, but I’m not going to bother, and we can just slap {map_embed_code} into our {exp:channel:entries} loop by itself.

So our final contact/index template should look like this:

{layout="layouts/_wrapper"}

{exp:channel:entries channel="contact" limit="1"}
  <h1>{if page_heading}{page_heading}{if:else}{title}{/if}</h1>
  {page_intro}
  <div class="row align-items-center">
    <div class="col-md-6">
      {address}
        <h2>Address</h2>
        <p>
          {address:line_1}
          {if address:line_2}<br />{address:line_2}{/if}
          <br />{address:city}, {address:state} {address:zip_code}
        </p>
      {/address}
      {departments}
        {if departments:count == 1}<h2>Departments</h2>{/if}
        <p>
          <strong>{departments:name}</strong>
          <a href="tel:+1-{departments:phone}">{departments:phone}</a>
          <a href="mailto:{departments:email}">{departments:email}</a>
        </p>
      {/departments}
    </div>
    <div class="col-md-6">
      {map_embed_code}
    </div>
  </div>
{/exp:channel:entries}

Step 3 - Building the About Page

Our last Single Entry template is our About page, and again, we’ll need a new Template Group so we can use the about/index template to achieve our example.com/about URL:

  1. In the CP, go to Developer > Templates, then use the blue New button next to the Template Groups heading in the left sidebar
  2. This time, let’s select ‘contact’ under the Duplicate existing group? section - this will duplicate all the templates inside the group and save us a little time
  3. Open about/index with your text editor, update the channel parameter to use about and strip out the fields specific to the Contact channel (our Bootstrap row with the Address, Departments and Map Embed Code):
    {layout="layouts/_wrapper"}
    
    {exp:channel:entries channel="about" limit="1"}
      <h1>{if page_heading}{page_heading}{if:else}{title}{/if}</h1>
      {page_intro}
    {/exp:channel:entries}
    
  4. Now we have to do the same thing we did for the Contact template and add the new fields we haven’t seen yet… so here is where we build out the templates for our Page Builder Fluid Field. This will be a long sub-step, but stick with me!

    Remember, a Fluid Field is a single field we can assign to a Channel that lets the content editor add multiple other fields to the page - in whatever order they want - and however many they want. In previous chapters, we planned out the fields available to our Page Builder and then set up those fields (and the Page Builder field itself) in the CP.

    You can read up on the Template Tags for a Fluid Field here, but the syntax is kind of similar to a Grid field in that you have your main field variable pair, and inside that, you have your ‘sub-fields’ referenced with another variable pair with the concatenated field and sub-field short names, like this:

    {fluid_field}
    
      {fluid_field:sub_field}
        ...
      {/fluid_field:sub_field}
    
    {/fluid_field}
    

    But the difference with a Fluid Field is that any content inside the sub-field is referenced with the special {content} tag - which can be either a single variable or a variable pair depending on the sub-field fieldtype. Any code inside this inner {fluid_field:sub_field} variable pair will render each time that sub-field is used within Fluid Field, regardless of whether the {content} tag loops or not.

    Page Builder: Centered Heading Sub-field

    So in our example, the Centered Heading field is just a Text Input field, so would normally just need a single variable, so in our case, we can output the data within the Page Builder like this:

    {page_builder}
    
      {page_builder:ff_centered_heading}
        {content}
      {/page_builder:ff_centered_heading}
    
    {/page_builder}
    

    Of course, this just outputs the content with no styling, so we need to add HTML around it. Remember, all code inside the {page_builder:ff_centered_heading} tag pair renders for each use. In this case, we’ll just wrap a <h2> with a Bootstrap class to center it, and we’re done:

    {page_builder}
    
      {page_builder:ff_centered_heading}
        <h2 class="text-center">{content}</h2>
      {/page_builder:ff_centered_heading}
    
    {/page_builder}
    

    Page Builder: Rich Text Sub-field

    Our Rich Text field, however, is a Grid, which normally uses a variable pair to get to the columns of the Grid, so our {content} tag needs to be used as yet another variable pair and anything inside this pair is looped through for as many rows as there are in the Grid:

    {page_builder}
    
      {page_builder:ff_rich_text}
        {content}
            ...
        {/content}
      {/page_builder:ff_rich_text}
    
    {/page_builder}
    

    Then inside the {content} variable pair, you reference the individual fields with {content:field_short_name}, so for our Rich Text field, try starting with this and refresh to see how it looks:

    {page_builder}
    
      {page_builder:ff_rich_text}
        {content}
          {content:text}<br />
          {content:width}<br />
          {content:background}<br />
          {content:center_content_container}
        {/content}
      {/page_builder:ff_rich_text}
    
    {/page_builder}
    

    Of course, this needs fleshing out - the Width, Background, and Center Content Container? fields aren’t intended to be displayed to the page but rather used in the HTML to adjust the display. Width is a Select Dropdown and Background is a Radio Button Group and in both cases, the content we have access to is the ‘value’ part of the Value/Label pairs we set up when creating these fields. We strategically set up both of those with actual class names. Center Content Container? is a Toggle field, which is a Boolean - either on (1) or off (0), so we can use a conditional to check that and add a class accordingly:

    {page_builder}
    
      {page_builder:ff_rich_text}
        {content}
          <div class="row {if content:center_content_container == 1}justify-content-center{/if}">
            <div class="{content:width} {content:background}">
              {content:text}
            </div>
          </div>
        {/content}
      {/page_builder:ff_rich_text}
    
    {/page_builder}
    

    IMPORTANT!

    So now we’ve seen two examples of how to build sub-field template code inside a Fluid Field, but in both examples, I showed the main {page_builder} variable pair with just the individual sub-field variable pair inside of it to make it easier to follow. To actually let the Page Builder field render all of the sub-fields as the content editor mixes and matches them, we have to have all sub-field variable pairs present inside one main {page_builder} pair. In other words, each of the sub-fields available to our Fluid Field should have a corresponding variable pair inside the main {page_builder} pair. So combining the two we’ve done so far, we get this:

    {page_builder}
    
      {page_builder:ff_centered_heading}
        <h2 class="text-center">{content}</h2>
      {/page_builder:ff_centered_heading}
    
      {page_builder:ff_rich_text}
        {content}
          <div class="row {if content:center_content_container == 1}justify-content-center{/if}">
            <div class="{content:width} {content:background}">
              {content:text}
            </div>
          </div>
        {/content}
      {/page_builder:ff_rich_text}
    
    {/page_builder}
    

    And of course, this must be inside our About page template’s Channel Entries tag, so our about/index template at this point should look like this

    {layout="layouts/_wrapper"}
    
    {exp:channel:entries channel="about" limit="1"}
      <h1>{if page_heading}{page_heading}{if:else}{title}{/if}</h1>
      {page_intro}
    
      {page_builder}
      
        {page_builder:ff_centered_heading}
          <h2 class="text-center">{content}</h2>
        {/page_builder:ff_centered_heading}
      
        {page_builder:ff_rich_text}
          {content}
            <div class="row {if content:center_content_container == 1}justify-content-center{/if}">
              <div class="{content:width} {content:background}">
                {content:text}
              </div>
            </div>
          {/content}
        {/page_builder:ff_rich_text}
      
      {/page_builder}
      
    {/exp:channel:entries}
    

    Page Builder: Document Downloads Sub-field

    For the Document Downloads sub-field, this a File Grid, so it will look pretty similar to our last example, but this time, it could have multiple rows within the grid. I’ll also take the opportunity to showcase what we can do with a File field when using its variable pair (docs here).

    NOTE: As we look to add the template code for more sub-fields available to our Page Builder, remember the code will also go inside the main {page_builder} pair, but for the sake of space, I’ll just be showing the sub-field variable pair.

    So for this sub-field, the template code to add to our {page_builder} field is:

    {page_builder:ff_document_downloads}
      <h2>Document Downloads</h2>
      {content}
        <p>
          {content:file}
            <a href="{url}" target="_blank">{content:title} ({extension}, {file_size:human})</a><br />
          {/content:file}
          {content:summary}
        </p>
      {/content}
    {/page_builder:ff_document_downloads}
    

    Things to note:

    • The code outside of the {content} variable pair only gets rendered once for each use of the sub-field inside the Page Builder - so the <h2> will only display once even if there are 30 files - unless the content editor adds the Document Downloads filed to the Page Builder multiple times.
    • Note the use of the ‘file’ variable pair - I did that so we could output the extension and filesize, which uses the handy :human modifier per the docs.

      Page Builder: Rich Text & Image Sub-field

    This one will be pretty similar to our regular Rich Text sub-field - this time we used a File Grid because each row contains an image. Remember we get access to the file in a File Grid using the file short name, and just like in other Fluid Field sub-fields, we reference that from within the {contact} variable pair and using the two-part concatenated tag, i.e.: {content:file}.

    We’re also going to check the content of our Image Position dropdown and add a Bootstrap class to flip the order if the users want the image on the right.

    {page_builder:ff_rich_text_image}
      {content}
        <div class="row  align-items-center {if content:image_position == 'right'}flex-md-row-reverse{/if}">
          <div class="col-md-6">
            <img src="{content:file:resize width='540'}" alt="{content:image_caption}" />
          </div>
          <div class="col-md-6">
            {content:text}
          </div>
        </div>
      {/content}
    {/page_builder:ff_rich_text_image}
    

    Page Builder: Featured Work Sub-field

    We’re almost there - this is the last sub-field! This sub-field uses a Relationship field which allows the content editor to ‘relate to’ - or pull in - specific Entries from our Work Channel. But remember, we’ve already built this functionality out in our Homepage template, just there, we did it through a Channel Entries Tag. We can re-use that code and just adjust it so that instead of looping through an {exp:channel:entries} loop, we loop through the selections in the Relationship field. Here’s the documentation on Relationships, but the syntax differs just a little big when referencing a Relationship from inside a Fluid Field (see here).

    So here we will re-use the second {exp:channel:entries} tag from our Homepage template (site/index), but make the following changes:

    • Firstly, it all has to go within our {page_builder:ff_featured_work} sub-field variable pair - remember anything inside this field gets rendered in each place the editor adds our Featured Work sub-field to the Page Builder
    • Previously, we used the {exp:channel:entries} tags to loop through the latest 3 entries. This time, the entries included in the loop depend on the Relationship field, so each selection is looped through in the {content} variable pair. So we just replace the {exp:channel:entries} opening and closing tags with {content} and {/content}
    • Because the individual fields for each Work Entry in the loop are now inside a {content} variable pair, we have to reference them accordingly using {content:field_name}, so we have to go through and add the content: part to each of our individual fields before they’ll output

    So here is our final code for this sub-field:

    {page_builder:ff_featured_work}
      <div class="row">
        {content}
          <div class="col-4">
            <h2><a href="{path='work/{content:url_title}'}">{content:title}</a></h2>
            <p><em>{content:entry_date format="%F %d %Y"}</em></p>
            {content:work_images limit="1"}
              <img src="{work_images:file}" alt="{work_images:caption}" />
            {/content:work_images}
            {if content:client_name}
              <h3>{content:client_name}</h3>
            {/if}
            <p><a href="{path='work/{content:url_title}'}">Read More ></a></p>
          </div>
        {/content}
      </div>
    {/page_builder:ff_featured_work}
    

    Putting Our Page Builder All Together

    Remember, we must have each of the individual sub-field variable pairs inside our main {page_builder} variable pair, so our final code for our Page Builder Fluid Field looks like this:

    {page_builder}
    
      {page_builder:ff_centered_heading}
        <h2 class="text-center">{content}</h2>
      {/page_builder:ff_centered_heading}
    
      {page_builder:ff_rich_text}
        {content}
          <div class="row {if content:center_content_container == 1}justify-content-center{/if}">
            <div class="{content:width} {content:background}">
              {content:text}
            </div>
          </div>
        {/content}
      {/page_builder:ff_rich_text}
    
      {page_builder:ff_document_downloads}
        <h2>Document Downloads</h2>
        {content}
          <p>
            {content:file}
              <a class="button" href="{url}" target="_blank">{content:title} ({extension}, {file_size:human})</a><br />
            {/content:file}
            {content:summary}
          </p>
        {/content}
      {/page_builder:ff_document_downloads}
    
      {page_builder:ff_rich_text_image}
        {content}
          <div class="row  align-items-center {if content:image_position == 'right'}flex-md-row-reverse{/if}">
            <div class="col-md-6">
              <img src="{content:file}" alt="{content:image_caption}" />
            </div>
            <div class="col-md-6">
              {content:text}
            </div>
          </div>
        {/content}
      {/page_builder:ff_rich_text_image}
    
      {page_builder:ff_featured_work}
        <div class="row">
          {content}
            <div class="col-4">
              <h2><a href="{path='work/{content:url_title}'}">{content:title}</a></h2>
              <p><em>{content:entry_date format="%F %d %Y"}</em></p>
              {content:work_images limit="1"}
                <img src="{work_images:file}" alt="{work_images:caption}" />
              {/content:work_images}
              {if content:client_name}
                <h3>{content:client_name}</h3>
              {/if}
              <p><a href="{path='work/{content:url_title}'}">Read More ></a></p>
            </div>
          {/content}
        </div>
      {/page_builder:ff_featured_work}
    
    {/page_builder}
    
  5. The last thing to do in our About page Template is actually in preparation for staying DRY in other templates. Right now, the Page Builder code is exclusively in our about/index template, but remember we’re also using the Page Builder in our Services Channel, which will use a different template. So that we can easily re-use this whole field, let’s put the whole thing (i.e., the code block above) into a Template Partial:

    • Go to Developer > Templates then go to Template Partials in the left sidebar
    • Click Create New then add the name par_page_builder_fluid_field and cut and paste in the whole {page_builder} variable pair from our about/index template, then save
    • Replace that whole variable pair in the about/index template with just our partial: {par_page_builder_fluid_field}

    Now our About page template looks much simpler, and we can re-use our Page_Builder field code. This is our entire about/index template now:

    
        <p>{layout="layouts/_wrapper"}</p>
        <p>{exp:channel:entries channel="about" limit="1"}
        <h1>{if page_heading}{page_heading}{if:else}{title}{/if}</h1>
        {page_intro}
        {par_page_builder_fluid_field}
        {/exp:channel:entries}</p>
        </pre>

What’s Next?

This was a long chapter, but we can re-use a lot of it in the next chapter. Now we have all of our Single Entry templates done, the next job to tackle is building out our Multi-entry templates - our Services section and our Work section.

Justin Alei's avatar
Justin Alei

Justin is an expert in ExpressionEngine and has created several award-winning sites for Consumer51 on the platform.