Blog Pagination, Categories and Comments with Structure in ExpressionEngine

by Jonathan Longnecker

If we haven’t made it plain before, we love ExpressionEngine and Structure. When betrothed together in holy matrimony they make a beautiful pair. But as with any relationship there are some snags. More specifically – blog pagination, categories and comments. Let’s take a look at the problems they have and then some quick tips I’ve stumbled across on how to fix them.

Basic Structure Setup

Before we jump in I’m assuming you know a bit about Structure. It lets you create pages based on URL’s not templates. But it does use templates. Confused yet!? Ok. If you’re creating a blog you will need to do a few things.

  1. Structure Blog ListingCreate two templates - A “show a list of my entries” template and an “individual article” template.
  2. In your Structure tree, go to settings and add your blog channel as a listing and set it to the individual article template.
  3. Create the main blog page with Structure, but choose your list of entries template instead of the default template. Also choose to make the page have listings and choose the Blog channel.
  4. Now back in your Structure tree you’ve got the blog page in your navigation and the option to edit or add new blog entries as listings.

Here’s basic versions of what the two templates should look like:

Example List Template

<!doctype html>
 <head>
     <title>{exp:structure:titletrail separator="|"}</title>
 </head>
 <body>
<div id="content">

    {exp:channel:entries channel="blog" dynamic="no" limit="10"}
    <article>
        <h1><a href="{page_url}">{title}</a></h1>
        {body}
    </article>
        {/exp:channel:entries}

    {paginate}<div class="paginate">
            View More: {pagination_links}</div>{/paginate}


    {/exp:channel:entries}    


</div><!--End Content-->

</body>
</html>

Example Article Template

<!doctype html>
 <head>
     {exp:channel:entries channel="blog" limit="1"}
    {if desc}<meta name="description" content="{desc}" />{/if}
    {if keywords}<meta name="keywords" content="{keywords}" />{/if}
    <title>{title} | Blog | {site_name}</title>

 {/exp:channel:entries}
 </head>
 <body>
<div id="content">

    {exp:channel:entries channel="blog" dynamic="no" limit="10"}
    <article>
        <h1><a href="{page_url}">{title}</a></h1>
        {body}
    </article>
    {/exp:channel:entries}

    <div id="comments">

        <h2>Comments</h2>

        {exp:comment:entries channel="blog"}
            <blockquote>
                <cite>
                    {url_or_email_as_author}<br />
                    {comment_date format='%M %d, %Y'}
                </cite>
                <article>{comment}</article>
            </blockquote>

            {/exp:comment:entries}

        </div><!--End Comments-->

    <h2>Form Header Here</h2>
        {exp:comment:form channel="blog"}

            <label for="name">Name</label>
            <input name="name" id="name" type="text" />

            <label for="email">Email Address</label>
            <input name="email" type="text" id="email" />

            <label  for="comment">Write your message here.</label>
            <textarea name="comment" id="comment">{comment}</textarea>

            <input name="submit" type="submit" value="Submit" />
            {/exp:comment:form}


    {/exp:channel:entries}    

</div><!--End Content-->

 </body>
 </html>

The Problems

Pagination

Doesn’t work at all. Why? Because if you have a URL like http://site.com/blog/P1 Structure doesn’t recognize the P1 segment and will either serve up a blank page or go to a 404. You could use Freebie and rebuild pagination yourself, but honestly…why? That’s too much work.

Categories

This doesn’t work either for the exact same reason. Structure doesn’t recognize the category URL’s. Obviously category pagination doesn’t work either. Again, you could use Freebie and rebuild categories with a bunch of if-statements. This would be a bit easier – but then if you needed to paginate categories with Freebie….well that sound you just heard was my head exploding.

Comments & URL Titles

Finally, because Structure uses the {page_url} variable instead of {url_title} EE seems to have a lot of trouble doing things like comments on the article page because it can’t find the Entry ID from the URL. My past solution is to make clients enter both the {url_title} and the {page_url} making sure they match. That’s a huge opportunity for mistakes, though and your clients have no idea what the heck you’re talking about.

Solutions

Pagination and Categories Solution

Blog Template GroupTo fix our category and pagination problem we are going to need to trick Structure. How do we do that? With a third template. Did you know that if Structure can’t find a {page_url}, but there is a template at the root of that directory it will try to read that instead? So in our blog example – if we setup our Structure page at http://site.com/blog we can go create a “blog” template group and copy the “list all your entries” template to the index template. Then we make a few modifications.

Modified List Template

<!doctype html>
<head>
{if segment_2 == "category"}
{exp:channel:category_heading channel="blog"}
<meta name="description" content="
    Blog entries in the {category_name} category." />
<title>{category_name} | Blog | {site_name}</title>
{/exp:channel:category_heading}
{if:else}
{redirect="404"}
{/if}
</head>
<body>
<div id="content">

    {if segment_2 == "category"}
    {exp:channel:category_heading channel="blog"}
        <h2>Viewing entries in the 
                    <strong>{category_name}</strong> category.</h2>
    {/exp:channel:category_heading}
    {/if}

    {if segment_2 == "" OR segment_2 == "category"}
    {exp:channel:entries channel="blog" limit="10"}
    <article>
        <h1><a href="{page_url}">{title}</a></h1>
        {body}
    </article>

        {paginate}<div class="paginate">
                    View More: {pagination_links}</div>{/paginate}
    {/exp:channel:entries}    
    {if:else}
    {redirect="404"}
    {/if} 

</div><!--End Content-->

</body>
</html>

We just add some if statements to check the segment for the category trigger in the URL. So now if Structure couldn’t find the http://site.com/blog/P1 or http://site.com/blog/category/category-name URL the fall back template will know what to do with it. Extra awesome bonus points: not only do pagination and categories work, but so does pagination in categories. I don’t know how I found this out, but it works great!

Comments and URL Title Solution

This one’s actually pretty easy and I think I ran across it in the Structure forums. There’s a special Structure tag to grab the ID out of the {page_url} so you can pass it to the comments and comment form.

Modified Article Template

<!doctype html>
<head>
{exp:channel:entries channel="blog" limit="1"}
{if desc}<meta name="description" content="{desc}" />{/if}
{if keywords}<meta name="keywords" content="{keywords}" />{/if}
<title>{title} | Blog | {site_name}</title>

{/exp:channel:entries}
</head>
<body>
<div id="content">

    {exp:channel:entries channel="blog" dynamic="no" limit="10"}
    <article>
        <h1><a href="{page_url}">{title}</a></h1>
        {body}
    </article>

    <div id="comments">

        <h2>Comments</h2>

        {exp:comment:entries channel="blog" 
              entry_id="{exp:structure:page_id}" parse="inward"}
            <blockquote>
                <cite>
                    {url_or_email_as_author}<br />
                    {comment_date format='%M %d, %Y'}
                </cite>
                <article>{comment}</article>
            </blockquote>

            {/exp:comment:entries}

        </div><!--End Comments-->

    <h2>Form Header Here</h2>
        {exp:comment:form channel="blog"
          entry_id="{exp:structure:page_id}" parse="inward"}

            <label for="name">Name</label>
            <input name="name" id="name" type="text" />

            <label for="email">Email Address</label>
            <input name="email" type="text" id="email" />

            <label  for="comment">Write your message here.</label>
            <textarea name="comment" id="comment">{comment}</textarea>

            <input name="submit" type="submit" value="Submit" />
            {/exp:comment:form}



    {/exp:channel:entries}    

</div><!--End Content-->

</body>
</html>

Just add entry_id="{exp:structure:page_id}" parse="inward" to the exp:comment:form and exp:comment:entries tags. Like I said, easy. Once I started using this my clients didn’t have to put in two URL Titles anymore! Much better. Did you find this useful? Am I doing it totally wrong and there’s a better way? Let us know in the comments.

September 29, 2011

ExpressionEngine

Comments

  1. Hi Jonathan,

    Thanks for posting about this topic. I did the same thing as you describe for a long time. But now, most of the time, I use the Zoo Triggers addon http://ee-zoo.com/add-ons/triggers This .addon allows you to fix this problem without duplicating a template. The other advantage is that my client could rename his structure page or even reposition it without any problem.

    Cheers,

    Bart

  2. @Bart - Cool man, thanks for sharing!

  3. This is a solid answer to a really frustrating problem. Thanks for working on it!

  4. @Doug Sure thing! I’m still not sure how I managed to stumble across it, but it’s been working well for me.

  5. Thanks for the writeup! Super useful.

    Native pagination was added in 3.0, so that one is as simple as adding the paginate param and proper pagination tags into your channel tags.

    Categories are something we’d love to get working better if we can get the proper hooks down the road.

    Thanks again!

  6. Nice, I’m going to use EE as soon as possible as a replacement for WP.

  7. Hi Johnathon,

    Quick question: How do you target non-existent categories?

    ex:
    http://www.domain.com/blog/category/general (works perfect)
    http://www.domain.com/blog/category/non-existent (hides category_heading and displays all posts).

    Is there anyway to throw a 404 at non-existent categories?

  8. I don’t think EE even supports that out of the box, does it? I haven’t found a simple way to do this yet. If you come up with something let us know!

  9. Thanks, Jonathan! I’ll see what I can find.

    I also stumbled upon an approach I haven’t tried yet using Seg2Cat (http://gotolow.com/addons/low-seg2cat). Erik Reagan describes his approach here (http://joviawebstudio.com/blog/guide_to_404_pages_with_expressionengine/ - about 20 comments in.)

  10. Sorry for the double post. Using Seg2Cat works perfectly with Structure and fixes the “non-existent category” problem. Below is a snippet of tested code that throws a 404 at categories that don’t exist.


    <!—Blog Home—>
    {if segment_2 == ‘’}
      Home

    <!—Single Category Listing—>
    {if:elseif segment_2 == “category” && segment_3_category_id != ‘’}
      Category listing

    <!—Article detail—>
    {if:elseif segment_2 != ‘’ && segment_2 != ‘category’}
      {exp:channel:entries channel=“post” limit=“1” dynamic=“yes” require_entry=“yes”}
      Single Article
      {if no_results}Sorry, no page was found.{/if}
      {/exp:channel:entries}

    <!—Blow up—>
    {if:else}
      {redirect=“404”}
    {/if}

  11. @nol Pretty cool! My only concern would be the weight and complexity from all the if statements. Got pounded on that at EECI this past week smile

  12. Thank you for this great explaination!

  13. Can you all explain how you handled archives using EE2 and Structure?  Any feedback on this would be helpful.  Thanks.

  14. @Lamar Pierce You mean date/category archives? In that blog folder where I’m providing that fallback index.html file I also put an archives template and bypass Structure all together.

  15. @Jonathan Longnecker Thanks for the reply. That makes sense.  Are you using EE conditionals to display in this format here on your site?  For example, when there is a new blog entry for May, the month heading will automatically display with the first new blog of the month?

  16. @Lamar Pierce We’re not using Structure on our site - there’s so few of those page types it didn’t really make sense. Just a standard channel loop with the date heading tags:

    http://expressionengine.com/user_guide/modules/channel/channel_entries.html#date-heading

  17. @Jonathan Longnecker Thanks for pointing me to the date heading tags info.  Really helpful.

Behold our Amazing Portfolio

Check it Out