Templating Intro - Templates, Template Groups & Staying DRY

In this chapter, we’ll be laying the foundations for working with EE templates. Technically a ‘template’ is just a container that outputs information. You might be tempted to think each template is a single page, but as we go through this tutorial, you’ll see they’re so much more than that and can be used in several different ways. Take the time to read through EE’s Template Overview page, then come back and continue.

The key takeaway is this:

“Templates can be considered as a single page of your site, but they’re much more than that. In ExpressionEngine, a template can be any of the following:

  • An entire webpage of your site.
  • A sub-section of your site, like a header or footer.
  • A page that can output various information types (RSS, CSS, HTML, XML, etc.) Because a Template is just a container that outputs information, you can create Templates for any type of data you need to present (RSS, CSS, HTML, XML, etc.). Templates can also be a smaller component of your page. Through the use of the Embed Tag you can insert a Template into another Template.”

In This Article:

Assuming you actually did read the Docs, you’ll have also seen that EE templates are stored in Template Groups and use a default routing path like example.com/template_group/template, but there are a few things worth noting:

NOTE: EE references templates by group and template name without the group and template extensions, so in the future, I’ll refer to the site.group/index.html template as site/index

So the first thing we’ll do is create our default Template Group and we’ll use the index.html template that’s generated when we create the group for our Homepage.

Step 1 - Create A Default Template Group

  1. Go to Developer > Templates from the main menu and note there are no Template Groups found. Use the blue New button to add our first Template Group: No template groups

  2. Note that the 'Make default group' toggle is enabled and name this first Template Group site as we'll use it for various global templates later on - use all lowercase for both template groups and template names. Then click Save Template Group to save.

  3. This will automatically create an index.html template inside the group, and because it's the default group, this will be the template that loads for example.com. These groups and templates are found in the filesystem at /system/user/templates/default_site/

  4. Also, note you can add Templates and Template Groups just by adding files and '.group' directories to the filesystem

  5. In your text editor, find the site.group directory and edit the index.html template. Then just add "Hello world!" and save it.

    NOTE: You can edit templates within the EE Control Panel if you wanted or were in a pinch by clicking the name of the template or the 'pencil' icon under the 'Manage' column:

    Index template

    Template changes will automatically sync regardless of where you edit them; however, working with a text editor is much more efficient, so recommended.

  6. Go to your browser and go to example.com (or whatever your setup is to get to the webroot) and note that our index.html template is now rendering - you should see your "Hello world!"

Step 2 - Expand Our site/index Template

Now that we can actually load a template and see the output, let’s expand on it so we have a workable framework. Remember, for this tutorial, we’re not going to get fancy with the frontend - once you have content output, you can always go back through and implement custom styles or integrate a pre-coded HTML ‘theme’ or however you typically work. In this tutorial, we will use the basic features of Bootstrap.

  1. Copy & paste in the ‘Starter Template’ from the latest version of Bootstrap ( found here at the time of writing - find the latest one if the link doesn’t work)

  2. In the <head>, update the <title> and add your own stylesheet after Bootstrap’s styles - for simplicity, I’ve just put the styles.css file in the webroot

  3. Add a simple header & footer with simple menu links and add containers to restrict content width

  4. Update “Hello World!” to an H1

So far, you should have something like this:

<!doctype html>
<html lang="en">
  <head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
  <link rel="stylesheet" href="/styles.css">

  <title>My Portfolio Site</title>
  </head>
  <body>
    <header>
      <div class="container">
        <a class="logo" href="/"><img src="/images/example.png" alt="Example" /></a>
        <ul class="nav">
          <li><a href="/services">Services</a></li>
          <li><a href="/work">Work</a></li>
          <li><a href="/about">About</a></li>
          <li><a href="/contact">Contact</a></li>
        </ul>
      </div>
    </header>

    <section class="container">
      <h1>Welcome To My Portfolio Site</h1>
    </section>

    <footer>
      <div class="container">
        <a class="logo" href="/"><img src="/images/example.png" alt="Example" /></a>
        <ul class="nav">
          <li><a href="/services">Services</a></li>
          <li><a href="/work">Work</a></li>
          <li><a href="/about">About</a></li>
          <li><a href="/contact">Contact</a></li>
        </ul>
      </div>
      <div class="copyright">
        <div class="container">
          &copy; 2020 Example Portfolio Site All rights reserved unless explicitly stated.
        </div>
      </div>
    </footer>

    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>

To add some basic styles, add the following to styles.css in the webroot:

/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed,  figure, figcaption, footer, header, hgroup,  menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; }
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; }
body { line-height: 1; }
ol, ul { list-style: none; }
blockquote, q { quotes: none; }
blockquote:before, blockquote:after,
q:before, q:after { content: ''; content: none; }
table { border-collapse: collapse; border-spacing: 0; }

/* Typograpy */
body { font-size: 18px; line-height: 1.3; }
h1, .h1 { font-size: 2em; margin-bottom: 20px; color: #0d86d6; }
h2 { font-size: 1.5em; margin-bottom: 15px; color: #0d86d6; }
h2 small { font-size: 0.7em; }
h3 { font-size: 1.2em; margin-bottom: 12px; }
img { max-width: 100%; height: initial; }
p { margin-bottom: 15px; }
em { font-style: italic; }
strong { font-weight: bold; }
a { color: #0d86d6; }
a:hover, a:focus { color: #0d86d6; text-decoration: underline; }
.button { background: #0d86d6; color: #FFF; padding: 5px 10px; }
.button:hover, .button:focus { background: #006cb9; color: #FFF; text-decoration: none; }

/* Example Photography Site styles */
header { background: #CCC; padding: 10px 0; margin-bottom: 30px; }
footer { background: #CCC; padding-top: 10px; margin-top: 30px; }
.copyright { background: #BBB; padding: 10px 0; margin-top: 10px; }
header .container, footer .container { display: flex; justify-content: space-between; align-items: center; }
.logo { display: inline-block; }
.nav { display: inline-flex; }
.nav a { padding: 10px; font-size: 1.1em; }
.nav a:hover, .nav a:focus { background: #0d86d6; color: #FFF; text-decoration: none; }
.carousel { margin: 45px 0; }
.carousel-caption { background: rgba(0,0,0,0.75); padding: 20px; }
.carousel-caption * { color: #FFF; }
iframe { width: 100%; max-width: 100%; }
.row { margin: 25px 0; }
.row > div.Black { background: #000; padding: 30px 30px 15px; color: #FFF; }
.row > div.Black *:not(a) { color: #FFF; }
.row > div.Grey { background: #CCC; padding: 30px 30px 15px; }
.gallery > div { margin-bottom: 15px; }
.gallery > a { display: block; margin: 15px 0; }
form input, form textarea { width: 100%; padding: 5px 10px; }
.paging a { display: inline-block; background: #F2F2F2; padding: 2px 8px; margin: 0 2px; }
.paging strong { display: inline-block; background: #0d86d6; color: #FFF; padding: 2px 8px; margin: 0 2px; }

Again, we’re not trying to get fancy with styling, and for simplicity, we’re just using plain ol’ CSS. You can, of course, build with whatever structure, workflow, compilers, etc. that you want, but we’re keeping it simple for this tutorial.

Step 3 - Stay DRY with Template Layouts

Now that we have global elements like the header and footer, we don’t ever want to have to duplicate that code, so we’ll implement Template Layouts right from the beginning.

  1. Create a new Template Group to hold our Layouts and a ‘wrapper’ template. Previously we went to Developer > Templates then used the New Template Group button to do this in the CP, but you can also add files to the filesystem, so try it that way this time:
    • Add a layouts.group folder under /system/user/templates/default_site/
    • Inside that directory, add an HTML template for our layout named _wrapper.html - the underscore is a convention indicating this template won’t be referenced by a URL. Instead, it’s a private template we’ll only be referencing from other templates
  2. Cut and paste the start and end of our site/index template to move everything except what’s inside our ‘container’ section to our layouts/_wrapper template

  3. Still in layouts/_wrapper, add the {layout:contents} variable where we want page contents to appear (i.e., inside our ‘container’ section)

  4. Back in the site/index template, specify what layout to use on the very first line with {layout="layouts/_wrapper"}

NOTE: If you now go back to Developer > Templates, you should see the new Template Group, and if you click into that, you should see the new _wrapper Template. You’ll also see the default index template, which we won’t be using, but that’s ok.

So now you should have two templates that have content in them:

<!doctype html>
<html lang="en">
  <head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
  <link rel="stylesheet" href="/styles.css">

  <title>My Portfolio Site</title>
  </head>
  <body>
    <header>
      <div class="container">
        <a class="logo" href="/"><img src="/images/example.png" alt="Example" /></a>
        <ul class="nav">
          <li><a href="/services">Services</a></li>
          <li><a href="/work">Work</a></li>
          <li><a href="/about">About</a></li>
          <li><a href="/contact">Contact</a></li>
        </ul>
      </div>
    </header>

    <section class="container">
      {layout:contents}
    </section>

    <footer>
      <div class="container">
        <a class="logo" href="/"><img src="/images/example.png" alt="Example" /></a>
        <ul class="nav">
          <li><a href="/services">Services</a></li>
          <li><a href="/work">Work</a></li>
          <li><a href="/about">About</a></li>
          <li><a href="/contact">Contact</a></li>
        </ul>
      </div>
      <div class="copyright">
        <div class="container">
          &copy; 2020 Example Portfolio Site All rights reserved unless explicitly stated.
        </div>
      </div>
    </footer>

    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>
{layout="layouts/_wrapper"}

<h1>Welcome To My Portfolio Site</h1>

Now when you visit example.com, EE loads our site/index template, which uses our layouts/_wrapper template and combines the two templates to give the result we want, but in a way, we can reuse the _wrapper for other pages and stay DRY. You should see exactly what you saw earlier. For a deeper look into Template Layouts, check out this video.

Step 4 - Stay DRY with Template Partials & Template Variables

So now we’ve utilized Template Layouts to separate our overall page wrapper from individual template code, but we still have some duplicated content in our wrapper.

Template Partials

Notice the menu links are duplicated in the header and footer, so if those ever need to be updated, you’d have to do it in two places. This is the perfect place to use a Template Partial, which are “are small bits of reusable template or tag parts,” which is perfect for this kind of thing. To start with, we’re just going to have static HTML, but it’s worth noting that partials can contain dynamic content too. Anything that could go in a regular Template can also go in a Template Partial and be reused.

So to set this up for our scenario:

  1. In the Control Panel, go to Developer > Templates, then toward the bottom of the left sidebar, go to Template Partials and click the blue Create New button

  2. Enter a Name, which becomes a single variable tag we can use in the templates. You want the name to be clear to what it is and have a clear naming convention, so you know it’s a partial (vs. a ‘variable’) and similar to Template Groups and Templates, I recommend all lowercase. So for this reason, I suggest the Name par_menu_links_list

  3. Paste in the code you want to be able to reuse - in this case, the complete <ul> tag, then save the partial

  1. In our layouts/_wrapper template, replace the duplicated <ul> code with our new partial variable: {par_menu_links_list}, eg:
    <header>
         <div class="container">
           <a class="logo" href="/"><img src="/images/example.png" alt="Example" /></a>
           {par_menu_links_list}
         </div>
       </header>
  1. Save and refresh your browser

    Once you create a Template Partial, you’ll see a new directory alongside your Template Groups in the file system named _partials - partials also get saved as files, so just like any other template, you can edit these with your text editor too. You can also create and use partials by adding .html files into this directory.

Template Variables

Template Variables are similar to Template Partials in how they’re created and used within templates, but they’re designed to hold static information that we might want the content editors to be able to change. Take a look at the Template Variable documentation do understand the differences between variables and partials. For naming conventions here, I always start with var_, in our example, go ahead and:

  1. In the Control Panel, go to Developer > Templates then in the left sidebar, click Template Variables and click the blue Create New button

  2. For the Name, enter var_copyright_text and paste into the Content: "All rights reserved unless explicitly stated." and save

  3. In our layouts/_wrapper template, replace that text with {var_copyright_text}, eg:

   <div class="copyright">
     <div class="container">
       © 2020 Example Portfolio Site {var_copyright_text}
     </div>
   </div>

Global Variables

ExpressionEngine also has several Global Variables we can use - it’s worth reviewing these , but specifically, in the code snippet above, we can use {site_name} to pull through the Site Name from the Settings and {current_time} with some Date Formatting to display the current year, with formatting it looks like this: {current_time format="%Y"}.

So pulling all of this together, our final layouts/_wrapper template looks like this:

<!doctype html>
<html lang="en">
  <head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
  <link rel="stylesheet" href="/styles.css">

  <title>My Portfolio Site</title>
  </head>
  <body>
    <header>
      <div class="container">
        <a class="logo" href="/"><img src="/images/example.png" alt="Example" /></a>
        {par_menu_links_list}
      </div>
    </header>

    <section class="container">
      {layout:contents}
    </section>

    <footer>
      <div class="container">
        <a class="logo" href="/"><img src="/images/example.png" alt="Example" /></a>
        {par_menu_links_list}
      </div>
      <div class="copyright">
        <div class="container">
          &copy; {current_time format="%Y"} {site_name} {var_copyright_text}
        </div>
      </div>
    </footer>

    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>

What’s Next?

Now we have a functioning template, but we currently only have one page working, and there’s not even any of our content that we carefully built up and added to the site through the Control Panel. In the next chapter, we’ll continue working with our site/index template and build out our other ‘Single Entry’ templates too.

Justin Alei's avatar
Justin Alei

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

Comments 0

Be the first to comment!