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!

 
Download the EE Code for Simple Multifaceted Navigation Using Categories

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

<< Previous Entry   

 

Previous Comments

Picture of Travis Smith

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.

Mike Boyink

by Mike Boyink (Author)

Date: Tuesday, June 23rd, 2009
Comment: #2

Good point Travis - I think I’ll leave the code and your comment as-is, but link to This EE Forum Thread where Robin explains the issue of elseif statements being less performance-savvy than serial if statements:

Yes- it will add overhead.  How much of a lag that translates to is hard to say.  But if you turn ‘show queries’ on- you’ll see that the queries are being run for both tags- only the display is altered.  It has to do with parse order- complex conditionals- the ‘else’ ones- are parsed after the tags.  Hence if you have 4 weblog tags inside a series of if:elses- all four are being processed.

Simple conditionals are parsed earlier- if you use those instead, you’d end up with only the tags inside the true conditions running.  And I do believe if you embed the content using the complex conditionals, the embeds are processed only if the conditional is true.  I didn’t actually test that last one- but if you want me to, I will.

Make sense?  It boils down to parse order.  And while it’s not going to be much lag- I personally wouldn’t set it up using the complex conditionals above- because no sense adding overhead when you don’t have to.

Picture of iain

by iain

Date: Thursday, July 23rd, 2009
Comment: #3

Thanks for posting the code Mike.

I found the ‘list’ template requires a dynamic=“off” to work properly, I kept only getting one result from the categories specified…

I’m not sure how to handle pagination for that template now, any ideas?..

Thanks.

Mike Boyink

by Mike Boyink (Author)

Date: Thursday, July 23rd, 2009
Comment: #4

He iain - thanks for catching that.  The code here was mildly modified from the code I used on the client site so wasn’t looking for >1 result.

Pagination..not sure.  Hadn’t needed it on my end.  Let me know what you find..;)

Picture of iain

by iain

Date: Thursday, July 23rd, 2009
Comment: #5

ha, here’s a surprise:

url_title="not {segment_3}|not {segment_4}" 

works!

Picture of iain

by iain

Date: Thursday, July 23rd, 2009
Comment: #6

Sorry for spamming your comments Mike,

Just a follow up on the code I posted above; statement after the pipe makes no difference to the call, my bad.

However pagination works fine with the following code:

url_title="not {segment_3}" 
Mike Boyink

by Mike Boyink (Author)

Date: Thursday, July 23rd, 2009
Comment: #7

No problem —glad to hear it!

Mike Boyink

by Mike Boyink (Author)

Date: Wednesday, July 21st, 2010
Comment: #8

Not sure - try:

AND ((exp_weblog_titles_1.status) <> 'closed')) 
Picture of Gary

by Gary

Date: Monday, December 13th, 2010
Comment: #9

Hi Mike,

I am building a news website which can make use of this feature. I am tying two categories (months, years) together for different types of news (weblogs). So why can I not use the same categories of months and years for different type of news weblogs? So that news from different sections gets filtered by month and year selected.

Gary.

Mike Boyink

by Mike Boyink (Author)

Date: Monday, December 13th, 2010
Comment: #10

Not sure?

Mike Boyink

by Mike Boyink (Author)

Date: Wednesday, December 15th, 2010
Comment: #11

Hey Paddy -

Thanks for the comments.

Without analyzing it in great depth it seems like you should be able to do what you need from different templates. 

You’ll just need to make heavy use of segment variables to look at what’s in the current URL and either change what’s there or append to it in the right situation.

Picture of James

by James

Date: Wednesday, October 5th, 2011
Comment: #12

I just implemented this navigation scheme on an EE2 site with only minimal adjustments.

It works like a charm! Thank you for this.

Add Your Comment

Commenting is not available in this channel entry.

Unless otherwise stated all content is © Michael Boyink of Train-ee.com & Boyink Interactive. Please don't steal - I've got kids to feed...

dy>