ExpressionEngine is fantastic at handling traditional web layouts. Let's start by clarifying what I mean by traditional web layouts. Every developer has received a series of PSDs and an accompanying sitemap made up of the following:
- a home page template
- secondary and tertiary page templates
- a various assortment of News, Events and Team pages, and other such unique snowflakes
ExpressionEngine, it would seem, is quite literally built to manage this kind of content and, at the time that the architecture for EE’s content model was developed, the aforementioned structure was the norm.
What Are Modular Page Layouts?
More and more, we're seeing designs that are based on modular content elements which can be arranged in any order, on any given page. Consider these wireframes made up of the following content elements:
- hero image
- full-column text
- two-column text
- three-column Text
- call to action
The standard ExpressionEngine field-group/channel/template architecture isn't going to work for this type of design because we don't know which content elements should appear on any given page or in what order.
How to Build Modular Page Layouts in ExpressionEngine
Let's use the wireframe above as an example of how we could build our modular page layouts. Let's say our example site has five main sections:
Each section is going to be made up of pages that consist of some combination of the content elements described above.
Our channel structure is going to be a little different than a typical ExpressionEngine build. We are going to create channels for each of the main sections of our site, prefixed with "-pages-".
We will end up with the following channels:
- -pages- Products
- -pages- Services
- -pages- Consulting
- -pages- Company
- -pages- Contact
We are also going to create channels for each of our content element types, prefixed with "-element-".
Our content element channels will be named:
- -element- Header
- -element- Hero
- -element- 1 Column Text
- -element- 2 Column Text
- -element- 3 Column Text
- -element- Call To Action
Channel Field Groups
The channel field groups for our -element- channels are going to, as you would imagine, be unique to the needs of each content element type. A nice benefit of this approach is that we can make each of the content elements a bit more flexible without making the entire experience too daunting for our end user. For example, let's consider the Call to Action channel. It will likely be made up of the following fields:
- Headline (Text Input)
- Intro (Textarea)
- Button Text (Text Input)
- Button URL (Text Input)
- Used On Pages (Playa Links - we'll discuss why this is important later)
However, to make it a bit more flexible, we would likely add an additional field for Layout (P&T Switch) with the following options:
- Button Left
- Button Right
You can imagine how this same type of flexibility could be added to each of the -element- channels to allow a level of creativity for the end user as they assemble pages.
Additionally, we always include fields for:
- Custom Class(es)
- Custom ID
This allows us to apply custom classes or IDs on the containing html for our content element. This way, if we need to do something a little special for a particular element, it's simply a matter of adjusting the CSS and targeting that element.
Unlike the -element- channels which are unique to the needs of their respective content element, all of the -pages- channels will be exactly the same. In fact, they can even use the same channel fields group if you'd like. In this example, we'll use one channel fields group to keep things simple. It will be made up of the following fields:
- Fieldtype: Playa - with all of the -element- channels selected
- Shortname: pages-elements
URL & Template
- Fieldtype: Better Pages - with our one and only pages template selected
- Shortname: pages-url
- Fieldtype: Playa Links
- Shortname: pages-related
At this point, I think you can see where I'm going with this. The end user will create content elements based on their needs in their respective channels. They will then create pages by simply using Playa to drag/drop the appropriate elements in the correct order. As you can imagine, another great benefit of this approach is that you can very easily distribute a single content element onto multiple pages. This, of course, will make updating syndicated content much faster.
I mentioned above, we will include Playa Links fieldtypes in all of the -element- and -pages- channel field groups. Without using Playa links, if a user wanted to update content on a particular page, their workflow might look something like this:
- Find the channel entry for the page their content is on.
- Examine the Playa field to determine which related -elements- are being used.
- Go back to the edit screen and search for that given -element-.
- Edit the correct element.
This is cumbersome and not the kind of experience we like to create for our ExpressionEngine users. We developed Playa Links to solve this very problem.
The Playa Links field which is used on the -pages- channels will create direct links to the related -elements- being used. The Playa Links field in the -elements- channels will alert the editor of which -pages- entries the content element they're editing is being included in.
Since we are only using one template in this example, we'll be making use of EllisLab's first-party Pages Module. This will give our end users the ability to determine the full URL of the page they are creating without having to adhere to strict template/url_title patterns.
In order to make this experience just a little better however, we'll use Better Pages to limit the choice of templates down to the only one that is appropriate in this case. This will be the pages/index template.
Templates and Snippets
We will keep all of our -element- template code in snippets and our one -pages- template in an actual template file (pages/index). We use Snippet Sync to manage all of our snippets as flat files and get them into Git. The other benefit of Snippet Sync is that it allows us to keep all of our -element- snippets in their own directory, so we'll create the following files:
If you don't use Snippet Sync, you should really check it out. It's a fantastic add-on and part of our core build.
Each of the -element- snippets will be unique to the site we are developing but the approach for the -pages- template will be somewhat standard and worthy of delving deeper into. Let's take a look at the template code:
Standard exp:channel:entries Tag
You'll see that we begin with a standard exp:channel:entries tag. The only difference here is that we have listed all the possible -pages- channels for it to respond to.
Elements Playa Field
The code actually starts to get interesting at line 10 where we enter the Playa loop which will work its way through the different elements we have assigned to this page.
Switchee To The Rescue (again)
I'm pretty sure that every ExpressionEngine developer has been saved by Mark Croxton's free Switchee plugin at least once. Here is another great example of its usefulness.
On line 11 we pass Switchee the short channel name of the current -element- channel entry that is being handled by the Playa loop. We have to also remember to instruct Switchee to parse inward.
Within Switchee's tag pair, we will set up a case for each of the possible -element- channels and then include the appropriate snippet for that -element-.
One Template to Rule Them All
That's really all there is to it. We've successfully built out a modular page layout which we can use across all the different sections of our site, using only one template, while still retaining the solid content architecture which makes ExpressionEngine so effective.
You use Causing Effect's CE Cache for every site you build, right? Right? I'm going to go ahead and assume the answer is yes because you absolutely should be. What we've done above can be somewhat heavy from a template processing perspective and you can mitigate any performance hits by using Aaron's incredible add-on.
It's All About the Add-ons
I think it is worth mentioning that this is a perfect example of what makes ExpressionEngine so great. Although the core of ExpressionEngine is robust, it's the incredible Add-on community that allows developers to handle tricky situations like these. To be clear, this approach wouldn't even be possible without the following add-ons: