Template “partials” functionality for ExpressionEngine: Embrace the DRY principle, reduce embed parsing and simplify your template ecosystem with this roll-your-own plugin.
SIDE NOTE: If you'd rather not hack together your own plugin, there are a number of 3rd party add-ons that can help you achieve the same approach outlined in this article; I've provided links to these alternatives at the bottom of the page.
Generally speaking, a “partial” is a template that can be passed, as a variable, to another template for output. Their purpose is to provide a convenient way to include common content across multiple templates. Most frameworks (RoR, CodeIgniter, FuelPHP etc) all have their own flavour of template partials, and ExpressionEngine’s embeds & snippets more or less the job - but not without a few downsides.
I'm going to discuss a technique that attempts to remedy these downsides. It will involve getting your hands dirty rolling our own EE plugin. It will be simple, limited, but hopefully demonstrate the potential at our fingertips.
What We Have Now
I’m willing to bet that if you were to open up any one of your templates in EE, it might look something like this:
{embed="embeds/_html_head"} <body class="blog"> <div id="wrapper"> {embed="embeds/_main_navigation" nav_active="blog"} <div id="content"> {exp:channel:entries ...} ... {/exp:channel:entries} </div> {snippet_sidebar} {embed="embeds/_footer"} </div> {embed="embeds/_html_foot"} </body> </html>
It’s a fairly standard setup, especially if you’ve crossed over from the world of Wordpress and are accustomed to functions like get_header()
& get_footer()
sprinkled throughout your theme files. And at a glance, it’s a super convenience, allowing you to include common content across your site. EE’s embeds and snippets are great.
But the downsides? I see three:
- Passing content into your embeds is cumbersome at best
- Each EE embed adds significant overhead to the parsing engine
- You still have to repeat that general markup structure for every template file that’s directly responsible for outputting content to the browser.
Now, downside #1 can be remedied somewhat by simply wrapping more of your template with your {exp:channel:entries}
tag, but it does little if you want to pass more than simple strings into an embed. Even so, downsides #2 and #3 remain, and these two are of greater concern. So let’s see if we can fix them all.
What We Need
We need a solution that solves our 3 problems above, plus meets an additional requirement I'd like to add:
- Reduced quantity of embeds
- DRY template pattern with a single "wrapper" template
- Chunks of content can easily be passed to that wrapper template
- Lightweight and bespoke plugin that meets our needs, our flavour of development, and frees us from relying on a 3rd party developer
Let me pause on the last point: I'm a huge proponent of self-reliance; of becoming familiar and comfortable with the core code base that you work on; and of tools that do one job and do that job well. EE plugins are surprisingly easy to build yourself, open up a whoop-ass can of power, and can even speed up your templates. You would do yourself a great service to learn how to roll your own EE plugins.
What It Will Look Like
So, we are going to build a "Partials" plugin that allows us to assign chunks of content to variables that are saved and then rendered later on by a single template. This "wrapper" template will contain our entire page structure, as well as markup that had previously been distributed across our earlier embeds (in our examples, those were _html_head
, _main_navigation
, _footer
and _html_foot
).
That's right, we are going to go from 4 template embeds down to 1.
Here's what our calling template will look like:
{embed="embeds/_wrapper" body_class="blog" nav_active="blog"} {exp:channel:entries ...} {exp:partials:set name="page_title"}{title}{/exp:partials:set} {exp:partials:set name="content"} {channel_body} {/exp:partials:set} {/exp:channel:entries}
You may notice we call our wrapper template first, but then "set" content later. Don't worry though, as the wrapper template will be embeded last, because of EE's parse order. However if you prefer, you're welcome to place the {embed}
call at the end of your template. It will work all the same.
Lines 3-6 are where the magic happens: we set two new partials and fill them with content. Our first partial is a simple string; our second contains our entire body content from our channel.
Let's look at our wrapper template now:
<!DOCTYPE html> <html> <head> <title>{exp:partials:get name="page_title"} | {site_name}</title> </head> <body class="{embed:body_class}"> <div id="wrapper"> <ul id="main_navigation" class="{embed:nav_active}"> ... </ul> <div id="content"> {exp:partials:get name="content"} </div> {snippet_sidebar} <div id="footer"> ... </div> {snippet_analytics} </div> </body> </html>
Look, no more embeds! What's more, this reads as a complete template; everything is in one place, and any changes to the foundation of your markup structure only needs to be made here. At line 4 we can see where we get one of our template partials, 'page_title'; then at line 12 we get our second, 'content'.
The Plugin, Finally
If you're wholly unfamiliar with EE plugins, head over to the User Guide and have a quick scan to get the basic idea.
And now, to the main event. Copy-and-paste this code into /system/expressionengine/third_party/partials/pi.partials.php
, and modify the first few lines to suit; that's right, put your name in there! After all this is going to be your plugin, not mine.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); $plugin_info = array( 'pi_name' => 'Homegrown Partials', 'pi_version' =>'1.0.0', 'pi_author' =>'Your Name Here', 'pi_author_url' => 'http://yoursite.com', 'pi_description' => 'Homegrown Partials - Template partials plugin for EE' ); class Partials { public $EE; /* * PHP5 constructor * get instance of EE, and set up our session cache */ public function __construct() { $this->EE = get_instance(); if ( ! array_key_exists('partials', $this->EE->session->cache)) { $this->EE->session->cache['partials'] = array(); } } // END /* * Set content in session */ public function set() { $name = strtolower($this->EE->TMPL->fetch_param('name')); $this->EE->session->cache['partials'][$name] = $this->EE->TMPL->tagdata; } // END /* * Get content from session */ public function get() { $name = strtolower($this->EE->TMPL->fetch_param('name')); if (array_key_exists($name, $this->EE->session->cache['partials']) { return $this->EE->session->cache['partials'][$name]; } } // END } /* End of file pi.partials.php */ /* Location: ./system/expressionengine/third_party/partials/pi.partials.php */
So the first half of our plugin is standard boilerplate stuff; set up our info array (EE needs this), and get an instance of the $EE object. Things get interesting around line 22, when we add an array to EE's session cache. All cache data is only available for this page request, and is wiped as soon as the request is over.
After that, we create two basic methods; one for {exp:partials:set}
and one for {exp:partials:get
}. Partials::set() retrieves our name parameter and passes all tagdata (everything between {exp:partials:set} ... {/exp:partials:set}
into our cache array, where $name
is the key. Then {exp:partials:get}
simply retrieves that cached tagdata based on the same $name
value.
And since this is ours, and not something we're releasing to the community, we don't need to be super safe - forgo the tedious checks if $name is a valid value, or if you might be overwriting the array value with something previously saved. So simple.
And if you are so inclined, improve upon it; build in a second parameter that flags whether you should overwrite or append your saved data. Add another parameter that will allow you to append or prepend strings to what is being printed. You could even build multiple variations of the Partials::get()
method to output variables differently, depending on your needs.
The possibilities are endless, and again since this is For Your Site Only, there's nothing to stop you!
3rd Party Solutions
This approach is in fact not new, but definitely not well-promoted; a few community developers have built fantastic and feature-rich add-ons that will help you achieve this technique if you'd rather not build your own plugin. A few of these are:
- MX Jumper - By max Lazar, for EE2, free
- REEposition - By Studo625, for EE1, free
- NSM Transplant - By Leevi Graham, for EE2, $AUD 34.95
- String Plugin - By eMarketSouth, for EE1.6x and EE2, $9.95
Have your say...