Templating Intro - Templates, Template Groups & Staying DRY
- 1 - Introduction to ExpressionEngine
- 2 - How ExpressionEngine Thinks About Content
- 3 - Planning the Content Model
- 4 - Installation & Basic EE Setup
- 5 - Building Our Content Model - Directories, Categories & Fields
- 6 - Building Our Content Model - Channels, Adding Content & Publish Layouts
- 7 - Templating Intro - Templates, Template Groups & Staying DRY
- 8 - Templating - Single Entry Templates
- 9 - Templating - Multi-entry Templates
- 10 - Final Touches - Contact Form, SEO Metadata, Error Handling & Performance Optimization
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:
- Step 1 - Create A Default Template Group
- Step 2 - Expand Our
site/index
Template - Step 3 - Stay DRY with Template Layouts
- Step 4 - Stay DRY with Template Partials & Template Variables
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:
- If there’s no 2nd segment in the requested URL, then the
index.html
from the group identified from the 1st segment will be loaded, e.g.example.com/work
will load thework/index
template - If two segments are present, e.g.
example.com/work/example-project
, but the second segment doesn’t have a correlating template, then EE will load theindex.html
template as a fallback - this is useful because we can use that second segment to identify individual entries - If no segments are there at all, the
index.html
template from the default Template Group will be loaded - You can use additional segments for various things, but it’s the first two that define what template is used - e.g.,
example.com/work/category/graphic-design
would load thework/category
template (if it exists), and inside that template, we would have access to{segment_3}
which we can use to actually decide which category to show - more on this later
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 assite/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
- Go to
Developer > Templates
from the main menu and note there are no Template Groups found. Use the blueNew
button to add our first Template Group: - 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 clickSave Template Group
to save. - 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 forexample.com
. These groups and templates are found in the filesystem at/system/user/templates/default_site/
- Also, note you can add Templates and Template Groups just by adding files and '.group' directories to the filesystem
- In your text editor, find the
site.group
directory and edit theindex.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:
Template changes will automatically sync regardless of where you edit them; however, working with a text editor is much more efficient, so recommended.
- Go to your browser and go to
example.com
(or whatever your setup is to get to the webroot) and note that ourindex.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.
- 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)
- In the
<head>
, update the<title>
and add your own stylesheet after Bootstrap’s styles - for simplicity, I’ve just put thestyles.css
file in the webroot - Add a simple header & footer with simple menu links and add containers to restrict content width
- 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">
© 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.
- Create a new Template Group to hold our Layouts and a ‘wrapper’ template. Previously we went to
Developer > Templates
then used theNew 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
- Add a
- Cut and paste the start and end of our
site/index
template to move everything except what’s inside our ‘container’ section to ourlayouts/_wrapper
template - Still in
layouts/_wrapper
, add the{layout:contents}
variable where we want page contents to appear (i.e., inside our ‘container’ section) - 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 defaultindex
template, which we won’t be using, but that’s ok.
So now you should have two templates that have content in them:
layouts/_wrapper
that handles all the shared code - ie, everything except our<h1>
:
<!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">
© 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>
site/index
which is effectively our Homepage template, and so far, it’s incredibly simple:
{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:
- In the Control Panel, go to
Developer > Templates
, then toward the bottom of the left sidebar, go toTemplate Partials
and click the blueCreate New
button - 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
- Paste in the code you want to be able to reuse - in this case, the complete
<ul>
tag, then save the partial
- 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>
- 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:
- In the Control Panel, go to
Developer > Templates
then in the left sidebar, clickTemplate Variables
and click the blueCreate New
button - For the Name, enter
var_copyright_text
and paste into the Content: "All rights reserved unless explicitly stated." and save - 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">
© {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.