Simple Multifaceted Navigation Using Categories
I needed a way to assign two category groups to one weblog, assign entries into categories in each group, and then build navigation based on those category assignments. However rather than the typical EE way of selecting one category and getting a list of results, I wanted another navigation level where after choosing a category from category group A the site would present a filtered list of categories from group B based on the selection from Group A.
Does that make sense? Here…maybe showing is better than telling in this case:
If that helped and you want to see how it was done…read on!
So just to recap - in this tutorial I have a weblog containing business names. The weblog has two category groups assigned to it - one category containing state names and the other business types. On the site I want to allow users to navigate to a final list of entries in two ways:
- Select a state
- Get a list of valid (only those actually containing results) business types for that state
- Select a business type
- Get a list of businesses of that type doing business in that state
or
- Select a business type
- Get a list of valid states (only those actually containing results) having that business type in them
- Select a state
- Get a list of businesses of that type doing business in that state.
ExpressionEngine lets you assign multiple category groups to one weblog - which from a content entry and maintenance point of view gets me where I want to be. However there is no built-in way of getting the categories to interact with each other as outlined above - where second-level category list is filtered by a choice from a first-level category group.
I see this as navigation based on a very simplistic faceted classification or what I’ve seen called “multifaceted navigation”. I’m not sure how far you could push this approach with more than two facets but this should provide a good jumping-off point.
Assumptions
- Your categories are only one layer deep - no nested subcategories
- Your category url titles are unique
Setup Requirements
Since this is a more advanced tutorial I’m going to assume you can get through the following without step by step instructions.
- Weblog named “businesses”. For the purposes of this tutorial I have no additional custom fields assigned - all I’m using is the default title field
- Two category groups - one for states and one for business types
- Some categories created within the category groups - again all with unique category_url_titles
- Knowledge of the category group IDs
- No other weblog using those category groups
- A number of entries posted into the weblog and assigned to various combinations of categories
- The EE Query Module (which is not available in the Core version)
- The Seg2Cat Extension from Lodewijk Schutte installed
The Index Template
I’ve included the templates used here as a downloadable zip file - but let’s walk through the logic here a template at a time.
The index template has the following code:
{if segment_2 ==""}
<h2>Select a State</h2>
{exp:weblog:categories weblog="businesses" category_group="2" show_empty="no" }
<a href="/index.php/businesses/in/{category_url_title}/">{category_name}</a>
{/exp:weblog:categories}
<h2>or Select a Business Type</h2>
{exp:weblog:categories weblog="businesses" category_group="4" show_empty="no"}
<a href="/index.php/businesses/type/{category_url_title}/">{category_name}</a>
{/exp:weblog:categories}
{if:elseif segment_2=="in"}
<h2>Business Types in {segment_3_category_name}</h2>
{embed="businesses/get_categories" from_category_group="4"}
{if:elseif segment_2=="type"}
<h2>States with {segment_3_category_name} Businesses</h2>
{embed="businesses/get_categories" from_category_group="2"}
{/if}
The first clause in the if statement handles the “index” view of the template before any choice has been made. All I’m doing there is using the standard weblog categories tag to return the top-level choices for each group. Do note the links that I’m building are not traditional EE category links - I’m rolling my own here. Businesses is the name of the template group I’m operating out of. This is the index template, so everything else is virtual. In the next segment I use either “in” or “type” keywords, then append on the category url title. Clicking any of these links reloads this template (because there are no actual saved templates with the names of “in” or “type”).
When the template is re-loaded after clicking a top-level category link, one of the next two clauses in the if statement fires, and this is where the Seg2Cat extension kicks in by providing the {segment_3_category_name} variable, which looks at segment 3, sees the category_url_title and returns the category name for it.
Now EE needs to take the selected category, look through the entries in the businesses weblog in that category, and see what categories those entries are assigned to in the other category group. That happens in the embedded “get_categories” template—and that template is made reusable by passing the ID of the category group we want results from. Here’s the code in that template:
The Embedded Template
<ul>
{exp:query sql="
SELECT exp_categories_1.cat_id AS my_cat_id, exp_categories_1.cat_name AS my_cat_name, exp_categories_1.cat_url_title AS my_cat_url_title
FROM (((exp_categories INNER JOIN exp_category_posts ON exp_categories.cat_id = exp_category_posts.cat_id)
INNER JOIN exp_category_posts AS exp_category_posts_1 ON exp_category_posts.entry_id = exp_category_posts_1.entry_id)
INNER JOIN exp_categories AS exp_categories_1 ON exp_category_posts_1.cat_id = exp_categories_1.cat_id)
INNER JOIN exp_weblog_titles AS exp_weblog_titles_1 ON exp_category_posts.entry_id = exp_weblog_titles_1.entry_id
WHERE
(((exp_categories_1.parent_id)=0)
AND ((exp_categories.cat_id)={segment_3_category_id})
AND ((exp_categories_1.group_id)={embed:from_category_group})
AND ((exp_weblog_titles_1.status)='open'))
GROUP BY exp_categories_1.cat_id, exp_categories_1.cat_name, exp_categories_1.cat_url_title, exp_categories_1.cat_order
ORDER BY exp_categories_1.cat_order;"}
<li>
<a href="/index.php/businesses/list/{segment_3}/{my_cat_url_title}/">{my_cat_name}</a>
</li>
{/exp:query}
</ul>
This query essentially:
- Takes a known category ID
- Takes a known category group ID
- Finds entries assigned to the known category ID
- Makes sure those entries have an “open” status
- Finds what other categories those entries have been assigned to—from the category group represented by the known category group ID
- Builds a list of the unique values it finds
- Creates links that contain url_titles from both categories
- Orders those results as per the order specified in the Control Panel
Note - for this example I’m not specifying a weblog. If you need to narrow the results to a specific weblog you can do that by appending onto the queries WHERE clause and specify the weblog ID. You could also pass that in as an embedded variable if you needed this embedded template to be flexible in what weblog it looked at.
Again the magic happening in this code is provided by the Seg2Cat extension that gives access to a category ID based on the category url title currently being displayed. The extension comes in handy yet again on the final template - the list template:
The List Template
<h2>{segment_3_category_name} {segment_4_category_name} Businesses</h2>
<ul>
{exp:weblog:entries weblog="businesses" category="{segment_3_category_id}&{segment_4_category_id}"}
<li>{title}</li>
{/exp:weblog:entries}
</ul>
Seg2Cat is necessary here to get around another limitation of the native weblog:entries tag—in that while it has the category parameter which can find only entries that exist in two categories, it needs category ID’s rather than the more SEO-friendly category_url_titles. Seg2Cat again steps in to provide the translation of category_url_titles to category IDs - so now I can have URLs like:
/index.php/businesses/list/alabama/big_box_retailer/
or
/index.php/businesses/list/big_box_retailer/alabama/
...and the weblog:entries tag can still function. Note also that while these URLs are using category_url_titles they are not using the typical /category/ segment needed when using EE categories.
So there you have it! While this tutorial is a bit more advanced, what with an extension, embedded template using an embedded variable, segment variables, and conditionals— it all comes together to create a nifty little front-end navigation approach with search-friendly URLs and easy client content maintenance.
Category Navigation







by Travis Smith
Date: Tuesday, June 23rd, 2009
Comment: #1
Nice and wonderful article:
Suggestion: I’d ditch the {elseif}s and just use serial IFs—save yourself from running that top IF code on each interior page load by using simple conditionals that are dealt with before the rest of the tags.