Faceted Categorization in MT.

| No Comments | No TrackBacks

Via Anil Dash. Tanya Rabourn (aka pixelcharmer) has posted an interesting and creative way in which she organizes her weblog using faceted categorization to achieve a simple hierarchical relationships between posts. (Hierarchical relationships is currently not supported in MT.) She details how she does it concluding This is additional evidence to back up my observation that most problems in life can be solved with the creative use of regular expressions.

I agree, but regular expressions inside of Movable Type templates make me cry though.

I've developed a proper MT plugin as a more elegant alternative. mt-categoryfaceted can be downloaded through my mt-plugins page.

If you can grok regular expressions then with a little extra effort you can create develop your very own plugin. It only takes a read of my Developing Movable Type Plug-ins article, some cut and paste and a wee bit of extra perl knowledge to achieve. The end result is that your solution is easy to update if you find a bug or want to add a feature and it's easily shared with others.

To demonstrate I'll take this opportunity to walk through the code of this plugin and show how I ported Tanya's solution into a plugin. I purposefully wrote this code in longhand so it's easier to read and to promote best practices.


package MT::Plugin::CategoryFaceted;

This line helps avoid any conflicts with other installed plugins. CategoryFaceted would be replaced with the name of your plugin.


use MT::Template::Context;

Load the MT module that houses all of the template tag functionality. This is all that it needed for a simple template tag plugin.


use vars qw( $VERSION );
$VERSION = '0.02';

Makes the version discoverable by other perl programs and modules. Not necessary, but a practice I recommend plugin developers do.


MT::Template::Context->add_global_filter(faceted => \&faceted );
MT::Template::Context->add_conditional_tag(CategoryIfFaceted => \&if_facet ); 

This is where the meat of a plugin starts. In these two lines we let MT know about the tags we are implementing and associate them to subroutines found later in the code. In the first, I'm registering a global filter named faceted and associating it (through a reference that the prepended \ provides) to the faceted subroutine. In the second, I'm registering a conditional tag CategoryIfFaceted (MT is assumed by the system) and associating it to the if_facet sub routine.


sub facet_split {
	my $label = shift;
	$label=~m/^([^:]+):\s+(.*)$/;
	return $1, $2;
}

Perhaps I was being a bit anal rententive here, but I create a utility subroutine so me the regular expression resides in one place and can be shared by all other subroutines. Unlike the pixelcharmer implementation, I don't get explicit about what facet I'm looking for in my regular expression. This adds flexibility to use facets other then PMEST and supports other languages.

In the first line I declare my subroutine and open it. In the second line I assign the first parameter passed into the routine to the variable label. (my causes $label to only be recognized inside of the subroutine.) In the third line we do our regular expression match. (I'll assume you understand your regular expressions already.) In the fourth line I return to values to the whatever called the routine in the first place. The variables $1 and $2 are system variables the hold the matching content of the first and second set of parenthesis (respectively) in the matching regular expression. In the last line we close the subroutine with a curly bracket.


sub faceted { 
	my ($text, $arg_value, $ctx) = @_;
	return $text unless ($arg_value);
	my ($facet, $label) = facet_split($text)
		if ($ctx->stash('tag')=~m/CategoryLabel|ArchiveCategory/);
	return $label || $text;
}

This routine is what is behind out global filter faceted. The second line we receive three variables that MT passes all global filters. The text (contents) of the tag the global filter is being applied to, the value that the global filter was assigned, and a reference to the context. Context gets a bit deep. To keep it simple lets say it's an object that holds the data MT is working with at the moment.

The third line is more anal rententiveness – if the global filter is called with 0 or is blank, don't do anything and pass back the text untouched. In the fourth line and fifth line (note: it's one command) we pass the text to our utility routine and get back the facet and category label only IF the tag is MTCategoryLabel or MTArchiveCategory. (Why? Because I don't want to have to support for some unattended use I'm not prepared for.) In the next line we return the label or, if the label is null the text we were originally passed. Passing a null value back will generate an error in MT and all processing will stop.


sub if_facet {
	my ($ctx, $args) = @_;
	$ctx->error('MT'.$ctx->stash('tag').' tag outside of the proper context.') 
		unless defined( $ctx->stash('category') ); 
	$ctx->error('facet is a required argument of '.$ctx->stash('tag').'.')
		unless defined( $args->{'facet'} ); 
	my ($facet, $label) = facet_split( $ctx->stash('category')->label );
	return $facet eq $args->{'facet'};
}

Next is the routine that is behind the conditional tag of MTCategoryIfFaceted. Second line we assign the context and any arguments (a hash reference who know what that is) that where declared with the tag from MT assigning them to variables. In the third through sixth lines we do two error checks. The first error check is to make sure the tag is being used within the context of a category. The second error check I make sure an argument of facet has been passed in because I can't continue processing without that. (In my error messages I like to use the tag variable $ctx->stash('tag') instead of just typing out the tag name. This way if I change the name of my tag I don't have to worry about changing it in the error message or potentially confusing a user when they get a message for a tag they aren't using. I also have the flexibility of having one subroutine backing multiple tags if necessary. But I digress.)

In the seventh line, once again, I call the utility method and get back the facet and category as separate values. In the eighth line I use a piece of perl shorthand (sorry I couldn't help myself) where I return the result of the comparison between the value of the facet argument and the facet of the current category being processed. Conditional tags expect either a 0 (false) or 1 (true) response. If they match, then a true (1) valued is return and MT processes the contents of the tag. If they don't match, then a false (0) value is return and MT does not process its contents and strips it form the output.


1;

This just lets perl know that all the code is loaded and properly.

This is a bit more involved then the solution that inspired it, but I think the benefits are worth the extra effort. I encourage any motivated and curious person to give it a try. It may be easier then you think.

UPDATE: Here is an example of the plugins usage. Taken from the synopsis of the embedded POD documentation.


	<MTArchiveCategory faceted="1" />

	<ul><MTCategories>
	<MTCategoryIfFaceted facet="Time">
	<li><MTCategoryLabel faceted="1"></li>
	</MTCategoryIfFaceted>
	<MTCategories></ul>

<p>Via <a href="http://www.dashes.com/anil/">Anil Dash</a>. Tanya Rabourn (aka pixelcharmer) has <a href="http://www.pixelcharmer.com/fieldnotes/archives/process_designing/2003/000348.html">posted</a> an interesting and creative way in which she organizes her weblog using faceted categorization to achieve a simple hierarchical relationships between posts. (Hierarchical relationships is currently not supported in MT.) She details how she does it concluding <q>This is additional evidence to back up my observation that most problems in life can be solved with the creative use of regular expressions.</q></p>
<p>I agree, but regular expressions inside of Movable Type templates make me cry though. </p>
<p>I&#39;ve developed a proper MT plugin as a more elegant alternative. mt-categoryfaceted can be downloaded through my <a href="http://tima.mplode.com/tima/files/mt-plugins/#mt-categoryfaceted">mt-plugins page</a>.</p>
<p>If you can grok regular expressions then with a little extra effort you can create develop your very own plugin. It only takes a read of my <a href="http://www.oreillynet.com/pub/a/javascript/2003/03/18/movabletype.html">Developing Movable Type Plug-ins</a> article, some cut and paste and a wee bit of extra perl knowledge to achieve. The end result is that your solution is easy to update if you find a bug or want to add a feature and it&#39;s easily shared with others.</p>
<p>To demonstrate I&#39;ll take this opportunity to walk through the code of this plugin and show how I ported Tanya&#39;s solution into a plugin. I purposefully wrote this code in <q>longhand</q> so it&#39;s easier to read and to promote best practices. </p>
<pre><code>
package MT::Plugin::CategoryFaceted;
</code></pre>
<p>This line helps avoid any conflicts with other installed plugins. CategoryFaceted would be replaced with the name of your plugin.</p>
<pre><code>
use MT::Template::Context;
</code></pre>
<p>Load the MT module that houses all of the template tag functionality. This is all that it needed for a simple template tag plugin.</p>
<pre><code>
use vars qw( $VERSION );
$VERSION = &#39;0.02&#39;;
</code></pre>
<p>Makes the version discoverable by other perl programs and modules. Not necessary, but a practice I recommend plugin developers do.</p>
<pre><code>
MT::Template::Context-&gt;add_global_filter(faceted =&gt; \&amp;faceted );
MT::Template::Context-&gt;add_conditional_tag(CategoryIfFaceted =&gt; \&amp;if_facet );
</code></pre>
<p>This is where the meat of a plugin starts. In these two lines we let MT know about the tags we are implementing and associate them to subroutines found later in the code. In the first, I&#39;m registering a global filter named <q>faceted</q> and associating it (through a reference that the prepended \ provides) to the <code>faceted</code> subroutine. In the second, I&#39;m registering a conditional tag <q>CategoryIfFaceted</q> (MT is assumed by the system) and associating it to the <code>if_facet</code> sub routine.</p>
<pre><code>
sub facet_split {
my $label = shift;
$label=~m/^([^:]+):\s+(.*)$/;
return $1, $2;
}
</code></pre>
<p>Perhaps I was being a bit anal rententive here, but I create a utility subroutine so me the regular expression resides in one place and can be shared by all other subroutines. Unlike the pixelcharmer implementation, I don&#39;t get explicit about what facet I&#39;m looking for in my regular expression. This adds flexibility to use facets other then <a href="http://www.asis.org/Publications/Thesaurus/21207.htm">PMEST</a> and supports other languages.</p>
<p>In the first line I declare my subroutine and open it. In the second line I assign the first parameter passed into the routine to the variable label. (<code>my</code> causes <code>$label</code> to only be recognized inside of the subroutine.) In the third line we do our regular expression match. (I&#39;ll assume you understand your regular expressions already.) In the fourth line I return to values to the whatever called the routine in the first place. The variables <code>$1</code> and <code>$2</code> are system variables the hold the matching content of the first and second set of parenthesis (respectively) in the matching regular expression. In the last line we close the subroutine with a curly bracket.</p>
<pre><code>
sub faceted {
my ($text, $arg_value, $ctx) = @_;
return $text unless ($arg_value);
my ($facet, $label) = facet_split($text)
if ($ctx-&gt;stash(&#39;tag&#39;)=~m/CategoryLabel|ArchiveCategory/);
return $label || $text;
}
</code></pre>
<p>This routine is what is behind out global filter faceted. The second line we receive three variables that MT passes all global filters. The text (contents) of the tag the global filter is being applied to, the value that the global filter was assigned, and a reference to the context. Context gets a bit deep. To keep it simple lets say it&#39;s an object that holds the data MT is working with at the moment. </p>
<p>The third line is more anal rententiveness &#8211; if the global filter is called with 0 or is blank, don&#39;t do anything and pass back the text untouched. In the fourth line and fifth line (note: it&#39;s one command) we pass the text to our utility routine and get back the facet and category label only IF the tag is MTCategoryLabel or MTArchiveCategory. (Why? Because I don&#39;t want to have to support for some unattended use I&#39;m not prepared for.) In the next line we return the label or, if the label is null the text we were originally passed. Passing a null value back will generate an error in MT and all processing will stop. </p>
<pre><code>
sub if_facet {
my ($ctx, $args) = @_;
$ctx-&gt;error(&#39;MT&#39;.$ctx-&gt;stash(&#39;tag&#39;).&#39; tag outside of the proper context.&#39;)
unless defined( $ctx-&gt;stash(&#39;category&#39;) );
$ctx-&gt;error(&#39;facet is a required argument of &#39;.$ctx-&gt;stash(&#39;tag&#39;).&#39;.&#39;)
unless defined( $args-&gt;{&#39;facet&#39;} );
my ($facet, $label) = facet_split( $ctx-&gt;stash(&#39;category&#39;)-&gt;label );
return $facet eq $args-&gt;{&#39;facet&#39;};
}
</code></pre>
<p>Next is the routine that is behind the conditional tag of <code>MTCategoryIfFaceted</code>. Second line we assign the context and any arguments (a hash reference who know what that is) that where declared with the tag from MT assigning them to variables. In the third through sixth lines we do two error checks. The first error check is to make sure the tag is being used within the context of a category. The second error check I make sure an argument of facet has been passed in because I can&#39;t continue processing without that. (In my error messages I like to use the tag variable <code>$ctx-&gt;stash(&#39;tag&#39;)</code> instead of just typing out the tag name. This way if I change the name of my tag I don&#39;t have to worry about changing it in the error message or potentially confusing a user when they get a message for a tag they aren&#39;t using. I also have the flexibility of having one subroutine backing multiple tags if necessary. But I digress.) </p>
<p>In the seventh line, once again, I call the utility method and get back the facet and category as separate values. In the eighth line I use a piece of perl shorthand (sorry I couldn&#39;t help myself) where I return the result of the comparison between the value of the <code>facet</code> argument and the facet of the current category being processed. Conditional tags expect either a 0 (false) or 1 (true) response. If they match, then a true (1) valued is return and MT processes the contents of the tag. If they don&#39;t match, then a false (0) value is return and MT does not process its contents and strips it form the output.</p>
<pre><code>
1;
</code></pre>
<p>This just lets perl know that all the code is loaded and properly.</p>
<p>This is a bit more involved then the solution that inspired it, but I think the benefits are worth the extra effort. I encourage any motivated and curious person to give it a try. It may be easier then you think.</p>
<p><strong>UPDATE:</strong> Here is an example of the plugins usage. Taken from the synopsis of the embedded POD documentation.</p>
<pre><code>
&lt;MTArchiveCategory faceted=&quot;1&quot; /&gt;

	&lt;ul&gt;&lt;MTCategories&gt;
	&lt;MTCategoryIfFaceted facet=&quot;Time&quot;&gt;
	&lt;li&gt;&lt;MTCategoryLabel faceted=&quot;1&quot;&gt;&lt;/li&gt;
	&lt;/MTCategoryIfFaceted&gt;
	&lt;MTCategories&gt;&lt;/ul&gt;
</code></pre>

No TrackBacks

TrackBack URL: http://appnel.com/mt/pings/123

Classification and MT from *Pixelcharmer: Field Notes on August 24, 2003 6:14 AM

Two new MT plug-ins have come into being since I posted about the way I use the category field here. Read More

CategoryFaceted from MT Plugin Directory on August 25, 2003 3:01 AM

Details on how this plugin was made are availalbe in this post. Inspired by an implementation of the scheme using... Read More

Via pixelcharmer, I noticed that a couple faceted navigation plugins have been created recently for Movable Type. I'm thinking of trying out the CategoryFaceted plugin. It'll probably be an improvement, but I doubt it'll do everything I want. Meanwhile... Read More

So, fürs erste bin ich fertig mit meiner MT-Anpassung.... Read More

So, fürs erste bin ich fertig mit meiner MT-Anpassung. Archiv-Struktur Statt http://wwworker.com/archives/0000815.html habe ich jetzt schicke Dateinamen, in denen alle wirklich wichtigen Infos stecken, wie zum Beispiel http://wwworker.com/archives/2003... Read More

Movable Type (MT) is a powerful web content publishing system, though difficult at first for beginners. Here are the links I have found most useful while implementing Movable Type weblogs. Support: Movable Type Support Forums - the best place to... Read More

Movable Type (MT) is a powerful web content publishing system, though difficult at first for beginners. Here are the links I have found most useful while implementing Movable Type weblogs. This list will grow as I continue to add features... Read More

I am the master of Faceted Categories! It was a hard trip, full of peril and understated difficulties, but finally,... Read More

Help, MT gurus! I want to build a faceted classification system so easy a child could use it, but I'm not finding it an easy task. I know about Pixelcharmer's solution as well as tima's plugin. They are not what I want. Unless I'm reading completely wr... Read More

Leave a comment

About this Entry

This page contains a single entry by Timothy Appnel published on August 22, 2003 8:51 PM.

Hacks: Object-Oriented Weblog Publishing was the previous entry in this blog.

Hacks: Inserting Frequently Updated Content In Movable Type. is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.