Sunday 10 November 2013

Orchard CMS Taxonomy Term Autoroute Token Slug v2.0

This is a new and improved solution to my original post http://sheltonial.blogspot.com.au/2013/10/orchard-cms-taxonomy-term-autoroute.html.

The purpose of this post is to provide a method of returning a full taxonomy term path slug as a token in Orchard CMS. I am currently using this solution in Orchard 1.7.1 as of changeset 91d0048f2796f093a8cefe2111ff013818d220fb.

The problem with the previous solution was that it only rendered the 1 selected taxonomy term into the token result, which was great if you were working with the root terms, but as soon as you went deeper it did not render the full taxonomy term path into the token result.

The following solution allows a full taxonomy term path to be slugified and rendered into a token result.

using System;
using System.Linq;
using Orchard.Taxonomies.Fields;
using Orchard.Localization;
using Orchard.Tokens;

namespace Orchard.Taxonomies.Tokens {
    public class TaxonomyTokens : ITokenProvider {

        public TaxonomyTokens() {
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }

        public void Describe(DescribeContext context) {
            // Usage:
            // Content.Fields.Article.Categories.Terms -> 'Science, Sports, Arts'
            // Content.Fields.Article.Categories.Terms:0 -> 'Science'

            // When used with an indexer, it can be chained with Content tokens
            // Content.Fields.Article.Categories.Terms:0.DisplayUrl -> http://...

            context.For("TaxonomyField", T("Taxonomy Field"), T("Tokens for Taxonomy Fields"))
                   .Token("Terms", T("Terms"), T("The terms (Content) associated with field."))
                   .Token("Terms[:*]", T("Terms"), T("A term by its index. Can be chained with Content tokens."))
                   .Token("TermPathSlug", T("Terms"), T("Selected taxonomy term full path slugified."))
                   ;
        }

        public void Evaluate(EvaluateContext context) {
            //context.For<TaxonomyField>("TaxonomyField").Data.TermsField.Value.ElementAt(0).Slug

            context.For<TaxonomyField>("TaxonomyField")
                   .Token("Terms", field => String.Join(", ", field.Terms.Select(t => t.Name).ToArray()))
                   .Token(
                       token => token.StartsWith("Terms:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Terms:".Length) : null,
                       (token, t) => {
                           var index = Convert.ToInt32(token);
                           return index + 1 > t.Terms.Count() ? null : t.Terms.ElementAt(index).Name;
                       })
                   .Token("TermPathSlug", field => context.For<TaxonomyField>("TaxonomyField").Data.TermsField.Value.ElementAt(0).Slug)
                // todo: extend Chain() in order to accept a filter like in Token() so that we can chain on an expression
                   .Chain("Terms:0", "Content", t => t.Terms.ElementAt(0))
                   .Chain("Terms:1", "Content", t => t.Terms.ElementAt(1))
                   .Chain("Terms:2", "Content", t => t.Terms.ElementAt(2))
                   .Chain("Terms:3", "Content", t => t.Terms.ElementAt(3))
                   ;
        }
    }
}

An example of an autoroute pattern that works is {Content.Fields.BlogPost.Category.TermPathSlug}, where [BlogPost] is the name of your content type, and [Category] is the name of the field within our content type, not the taxonomy or term.

This could be used is reflecting a taxonomy term hierarchy within a URL using an AutoRoute pattern.

Please be aware that this solution grabs the first selected taxonomy term, so your taxonomy field should be configured so that only 1 term can be selected.

Refer to https://orchard.codeplex.com/workitem/20215 regarding updates on the Orchard work request relating to this.

Hope this helps! Any questions or if you have an improved solution let me know.