Published by Mijingo

movie icon image

ExpressionEngine How-to Articles

Topics range from beginner to advanced, but are all born out of real world, professional, day-to-day use of ExpressionEngine. Need more information? Get training videos and ebooks on ExpressionEngine from Mijingo.

Simpler URLs with a Single Template

One of the greatest strengths of ExpressionEngine is its flexibility, so it’s no surprise that there are usually multiple ways of accomplishing the same task. But as you continue to work more and more with EE, you sometimes find better ways of doing things, whether it’s getting something done more efficiently or with less code (or hey, maybe both!).

One of the areas that I’ve always thought could be improved was the way templates are utilized to display content. I want to use a single template to display both the listing of entries and the individual entry. To explain what I mean, let’s look at the following URL (from the Agile Records theme demos site that you can install) after I’ve removed index.php:

http://domain.com/news/comments/welcome_to_the_example_site

After the domain bit, this URL consists of three parts

  1. segment_1: news (a template group)
  2. segment_2: comments (a template)
  3. segment_3: welcome_to_the_example_site (the url_title of the entry)

As you can tell, it’s using the comments template (which is inside of the news template group) to display single entries. Splitting it up into three parts like this was a good way for EllisLab to keep things simple and explain how template groups and templates work, but it has resulted in an entirely unecessary segment being added - “comments”. I think this is inefficient because:

  • This extra segment makes the URL longer and therefore harder to remember.
  • It’s always a good idea to try as much as possible to have URLs that represent different sections of the site so that if you remove the last segment of a URL you go back up a branch in the tree and view the parent information. However, with this structure if you remove the url_title of an entry and go to http://domain.com/news/comments you get thrown a 404 error. So in terms of usability it’s not that great.
  • In terms of search engine optimization, Matt Cutts of Google says it’s not necessarily a big deal to have that extra segment in there, but I found that saying it wasn’t a “major factor” to be a bit ambiguous, so I’d rather be on the safe side and keep the number of URL segments as low as possible, if it’s at all practical.

So here’s what we’re going to do: we’re going to eliminate the use of the comments template by using our index template to display an individual entry if a url_title is passed to it, while continuing to have it display a list of entries if it’s called with no extra segment at the end. Here’s how that’s done:

{if segment_2==""}
    {exp
:channel:entries 
        channel
="news" 
        
disable="categories|category_fields|member_data"
        
limit="10"
        
dynamic="no"
        
paginate="bottom"
        
}

        
<h1><a href="{site_url}news/{url_title}">{title}</a></h1>
        
{news_body}
        
        {paginate}
            
<p>Page {current_page} of {total_pages} pages {pagination_links}</p>
        
{/paginate}

    {
/exp:channel:entries}

{if
:else}
    {exp
:channel:entries 
        channel
="news" 
        
disable="categories|category_fields|member_data|pagination"
        
limit="1"
        
url_title="{segment_2}"
        
}    
        
        {if no_results}
            
<p>No entry found.</p>
        
{/if}

        
<h1>{title}</h1>
        
{news_body}

    {
/exp:channel:entries}   
        
{
/if} 

The first part checks to see if segment_2 exists, and if it doesn’t exist, it displays the ten most recent entries from our news channel. At this point our URL would be http://domain.com/news. It’s important that we set dynamic to no so that it doesn’t try to look in our URL for any indication of which entries we want to display, and as always, we disable anything that we’re not going to use. It then creates a link for each of our entries using our site_url variable, our template group and the url_title of that particular entry.

The second part of our conditional kicks in if segment_2 exists, i.e. when someone clicks on the link to one of the entries on our index template. An example of our URL at this point would be http://domain.com/news/welcome_to_the_example_site. It tries to pull an entry whose url_title matches segment_2 and displays it. If no url_title matches our segment, we make use of the if no results conditional to display an error message.

And there, we’ve done it! A cleaner, more memorable URL that has an excellent hierarchy. However, there are two small issues with this:

  • Pagination won’t work, because once the pagination string is added to the URL it becomes segment_2 and the second part of the conditional assumes that segment_2 is always the url_title of an entry, so the if no_results conditional kicks in.
  • Depending on how much work you’re doing in each conditional, a complex template can quickly become quite resource intensive as EE actually parses all of the conditions in a template then checks to see which one matches your criteria and discards the others.

The good news we can solve both of these issues with one little add-on.

Taking it Further - Adding Switchee to the mix

Switchee is a great little plugin that allows you to use case conditionals instead of “if:else” conditionals to speed up your templates. It does this by only evaluating code in the conditional that matches your criteria, so everything is no longer parsed. Here’s our template code modified to use Switchee’s syntax:

{exp:switchee variable="{segment_2}" parse="inward"
     
     {case value
="#^P(\d+)$#|''"}
         {exp
:channel:entries 
             channel
="news" 
             
disable="categories|category_fields|member_data"
             
limit="10"
             
dynamic="no"
             
paginate="bottom"
             
}
     
             
<h1><a href="{site_url}news/{url_title}">{title}</a></h1>
             
{news_body}
     
             {paginate}
                 
<p>Page {current_page} of {total_pages} pages {pagination_links}</p>
             
{/paginate}
     
         {
/exp:channel:entries}
        {
/case}     
     
     {case 
default="Yes"}
         {exp
:channel:entries 
             channel
="news" 
             
disable="categories|category_fields|member_data|pagination"
             
limit="1"
             
url_title="{segment_2}"
             
}   
     
             {if no_results}
                 
<p>No entry found.</p>
             
{/if}
     
             
<h1>{title}</h1>
             
{news_body}
     
         {
/exp:channel:entries}
     
     {
/case}
     
    {
/exp:switchee} 

You’ll see that in addition to checking for an empty segment_2 (’ ‘), our first case now also uses a regular expression to check segment_2 for a pagination string ("#^P(\d+)$#"). So now if you load the second page of a list of entries when accessing the index template and you have a pagination string in segment_2, e.g. P1, pagination will work nicely!

And as I mentioned before, because we’re using Switchee, whenever we load a URL it will check to see which case matches the criteria we’re looking for and only parse that case, so our template will be faster. When I was using the “if:else” conditional (without pagination and only two entries) the page used 28 queries to pull all the information that I required. When I used Switchee this dropped to 24, a pretty significant decrease, even though this is a very simple template.

So there we go: we’ve gotten simpler URLs and we’ve done it more efficiently and with less code than before!

Tony Geer
About Tony Geer

Tony has been designing and building websites for almost 10 years and has been privileged to work with some great clients from all over the world. You can learn more about his work at tonygeer.com.