Dynamic Categories On Subpages in ExpressionEngine

Posted on Mon, January 05, 2015 in Development Tips, by Kevin Carpenter

During a recent project, one of ExpressionEngine’s downfalls quickly forced me to think outside of EE’s built-in template syntax. The task was to allow the user to set ANY page to 1) A channel listing page (of any channel) 2) To START from any selected category and 3) Allow users to drill down into any category below the selected parent category. On the surface, this seems like it should be an easy task, particularly if you are not accustomed to ExpressionEngine’s syntax and limitations. Luckily, this downfall allows us to tackle this obstacle from many different approaches.

First, lets look at the template from which we will be calling our new template:

disable=" pagination|trackbacks"
<div class="white-bg main-content two-sidebar">
    <div class="container resized-width">
        <div class="row">
            <div class="col-xs-12 col-md-9 sidebar-left">
                    {embed="_structure/_sidebar_widgets" entry_id="{entry_id}"}
                <!-- need to add main-column class to accommodate new width css from mobile up -->
                <div class="col-xs-12 col-md-8 main-column">
                    <div class="clear"></div>
                    <div class="subpage-body document-list">
                        <div class="content-header subpage-body header-archive">
                            <div class="row">
                                <div class="col-xs-12">
                                    <ul class="document-list archive">
                                        {embed="_structure/categories_subpagefiles categoree="{document_category}{category_id}{/document_category}"}
                    <!-- end main content area homepage -->
                </div><!-- end col-xs-12 col-md-8 main-column -->

You’ll notice that we are requiring an entry to pull our initial data from, i.e. our “static page” or “subpage”. Which is essentially just a page to house all basic data (title, body text, sidebar elements,), along with the information to let EE know which entries to display (channel, category). You’ll also notice the snippet in which we will be referencing our template where the magic happens:

{embed="_structure/categories_subpagefiles categoree="{document_category}{category_id}{/document_category}"}

Here we are calling a new embed variable called “categoree” and setting it equal to a WB Category Select field, which will enable the user to select the category of entries to display on the page. Since EE needs category ID’s and not simply the name of the category, we have set this variable to the category ID of said category which the user will select.

The other half of this template (I’ll just include the relevant bit), is how we are going to set a category by using a GET variable. The reason why we need to set the category via a GET variable and not by passing it as a category segment, which contains either the url title or id of a category, is because if we do then EE will easily get lost on which page to return to retrieve our original page.

<a href="#SubMenu1-{cat_url_title}" class="list-group-item collapsed {if segment_2 == '{cat_url_title}'}active{/if}" data-toggle="collapse">{if has_children_in_output}<i class="fa fa-caret-down"></i>{/if}<span class="accordion-link" data-link="{current_url}?cat[]={cat_url_title}">{cat_name}</span></a>

This is simply just appending a GET variable containing the url title of each category in the sidebar. We will deal with this variable on the next template.

    $no_get = true;
    $param = '';
    foreach ($_GET AS $key => $value) {
        if (strtolower($key) != 'p') {
            $no_get = false;
    if ($no_get) {
        $param = 'category="{embed:categoree}"';
    else {
if (isset($_GET['cat']) && is_array($_GET['cat'])) {
//We are viewing a sub-category more specific/deeper than selected in entry field and it is not a pagination GET variable
foreach ($_GET['cat'] AS $url_value) {
$this->EE->db->where('exp_categories.cat_id = exp_category_posts.cat_id');
$this->EE->db->where('exp_categories.cat_url_title', $url_value);
$query = $this->EE->db->get();
$entry_ids = array();
foreach ($query->result() as $row)
$entry_ids[] = $row->entry_id;
$returned_list_of_entry_ids = implode('|', $entry_ids);
$param ="entry_id='".$returned_list_of_entry_ids."'";
    <?php echo $param; ?>
{if no_results}<p>No Documents</p></ul></div></div>{/if}
    <div class="row">
<div class="col-xs-12" style="width: 88%;">
            <p class="document-title"><a href="{exp:graybox:file_downloader file_id="{file}{file_id}{/file}"}">{title}</a> {if sticky == 'y'}<span class="pinned">icon</span> {/if}</p>

And finally, we have where the entry retrieving actually happens. This is what is going on in this snippet:

  1. We are first checking to see if a GET variable even exists in the URL. If it doesn’t, we know we are simply on the original subpage, and thus we are on the highest level category and we don’t need to do anything special since we already have the category ID we selected earlier from the entry, specifically in the WB Category Select field.
  2. If a GET variable does exist, we need to also make sure that we are not somehow on a pagination page (further security).
  3. We are setting our entire results in PHP equal to that of an entire EE channel entries parameter. Specifically, the entry list parameter. This way, if we aren’t specifically providing a list of relevant Entry_ID’s this parameter will simply ignore this, and work as normal by just selecting the category ID from the field.
  4. If the GET variable does indeed exist, we need to perform a few active record queries in order to provide EE a list of Entry_ID’s that match the category selected by the web user via the Category Sidebar widget. We simply grab the URL title from the variable, and perform a lookup to retrieve the category ID via an inner join method between categories and categories_posts tables and cat_id and cat_url_title columns. This gives us an Entry_ID array full of relevant Entry_ID’s separated by a pipe character.

It seems like a bit of work for such a simple task. But the fact is, unless you are able to assume at which segment these pages will exist, this method is entirely needed. Otherwise, we could just create a template group titled appropriately to our URL segments and any relevant templates. This setup was quickly shot down by the fact that this functionality had to truly be dynamic. With some ingenuity and a few lines of some PHP code, we were able to accomplish our results without having the need to extend our existing custom modules and extensions.

Don’t forget to enable PHP on this template, and happy coding! 

Tell Us About Your Project

Invalid phone number