Filtering an Item and Displaying the item based on Taxonomy
The most common way of using the built-in controls to show taxonomies both in the back-end and in the front-end is switching the of the field controls. However, this is not the most efficient way to display the taxonomies related to an item, and also users prefer the ability to filter the items directly by clicking the taxonomy of a specific item.
You can now use Sitefinity to create a field control that is lightweight and will serve to display all types of classifications – flat and hierarchical, built-in and custom, and on the front-end. By default, the field control will render the taxa as hyperlink, clicking which will construct the necessary query string, making the default widgets automatically filter the items by this taxa. You can also use this to display them as a simple text, as well, the way the built-in display them.
To filter the items and display the taxa items, perform the following:
Initializing the control
Create the control by inheriting them from Telerik.Sitefinity.Web.UI.Fields.FieldControl, and by reusing its implementation.
/// <
summary
>
/// Custom field control used to display Taxa for Sitefinity front-end
/// </
summary
>
public class CustomTaxonomyFieldControl : FieldControl
{
/// <
summary
>
/// Initializes a new instance of the <
see
cref
=
"CustomTaxonomyFieldControl"
/> class.
/// </
summary
>
public CustomTaxonomyFieldControl()
{
}
/// <
summary
>
/// Getting and setting the value as a TrackedList of type Guid, used for taxa
/// </
summary
>
[TypeConverter(typeof(ObjectStringConverter))]
public override object Value
{
get
{
return this.Taxa as TrackedList<
Guid
>;
}
set
{
this.Taxa = value as TrackedList<
Guid
>;
}
}
/// <
summary
>
/// The list of taxa passed to the field control
/// </
summary
>
protected TrackedList<
Guid
> Taxa { get; set; }
/// <
summary
>
/// The property showing whether the Taxon items would be shown as hyperlinks or as text
/// </
summary
>
public bool HideLinks { get; set; }
/// <
summary
>
/// Main Url of the site - used to build the Taxon Url
/// </
summary
>
protected string MainUrl { get; set; }
/// <
summary
>
/// Used to prepare and bind the data to the field control's controls when initializng
/// </
summary
>
/// <
param
name
=
"container"
>The container of the control</
param
>
protected override void InitializeControls(GenericContainer container)
{
List<
TaxonModel
> data = PrepareData();
BindData(data);
}
/// <
summary
>
/// We do not have any client side logic, so we use the inherited
/// ScriptDescriptor type of the Telerik.Sitefinity.Web.UI.Fields.FieldControl
/// </
summary
>
protected override string ScriptDescriptorType
{
get
{
return typeof(FieldControl).FullName;
}
}
}
In the code, you get the received value and try to save it as the expected type – TrackedList<Guid>. If the passed value is not of this expected type (invalid in this case), the Taxa property is Null.
NOTE:The value is handled later int he Binding section of the control.
The code above declares a property – HideLinks, which is used to indicate whether the Taxonomy items are displayed as hyperlinks – anchor html tags in the markup or as a simple text – a span elements in this case.
In the InitializeControls method, the data is prepared and bound to the control that will display it – a simple Repeater. It also handles any exception that might be thrown while preparing and binding the data.
Since the control does not need any client side logic that is different from the default one, the inherited FieldControl type is passed as the ScriptDescriptor one.
The field control is registered in the following way, if it is located in the SitefinityWebApp assembly, TaxonomyFieldControl namespace:
<%@ Register Assembly="SitefinityWebApp" Namespace="SitefinityWebApp.TaxonomyFieldControl" TagPrefix="custom" %>
Use your own assembly and namespace names to register it.
Then use the TagPrefix and the field control class to declare it in the widget template, where the taxa is displayed:
<
custom:CustomTaxonomyFieldControl
… />
Evaluate the field that contains the classification in the Value property:
<custom:CustomTaxonomyFieldControl
runat
=
"server"
ID
=
"CustomHierarchicalTaxonField"
value='<%# Eval("Category") %>' />
Preparing and Binding the data
Use the method below to use the property Taxa in which the passed value to the field control has been saved. It checks whether it is not null and it contains any taxa to be evaluated. If not, the control is hidden, since it has nothing to display. The method then uses the Id of the taxa to retrieve it. All taxa items are retrieved using the Ids in the Tracked<Guid> list.
/// <
summary
>
/// Preparing the Taxa to be bound. Convert the Taxa to DTO - TaxonModel model.
/// </
summary
>
/// <
returns
>The data to be bound.</
returns
>
private List<
TaxonModel
> PrepareData()
{
if (this.Taxa != null && this.Taxa.Count > 0)
{
TaxonomyManager manager = new TaxonomyManager();
IEnumerable<
Taxon
> taxaItems = manager.GetTaxa<
Taxon
>().Where(t => this.Taxa.Contains(t.Id));
List<
TaxonModel
> data = new List<
TaxonModel
>();
foreach (var taxon in taxaItems)
{
TaxonModel model = new TaxonModel();
model.Title = taxon.Title;
string taxonomyName = taxon.Taxonomy.Name;
string taxonUrl = string.Empty;
if (this.HideLinks == false) // HideLinks property default value
{
string url = GetMainUrl();
string evaluatedResult = BuilTaxonUrl(taxon, taxonomyName);
taxonUrl = string.Concat(url, evaluatedResult);
}
model.Url = taxonUrl;
data.Add(model);
}
this.TitleLabel.Text = string.Format("{0}:", taxaItems.First().Taxonomy.Name);
return data;
}
else
{
this.Visible = false;
return null;
}
}
/// <
summary
>
/// Bind the TaxonModel items to the Repeater, which will display them.
/// </
summary
>
/// <
param
name
=
"data"
>The TaxonModel list.</
param
>
private void BindData(List<
TaxonModel
> data)
{
this.RepeaterControl.DataSource = data;
this.RepeaterControl.DataBind();
}
The simple method below uses a very simple model to show the items, only with the necessary properties:
/// <
summary
>
/// Model representing Telerik.Sitefinity.Taxonomies.Model.Taxon
/// </
summary
>
public class TaxonModel
{
/// <
summary
>
/// Taxon Title
/// </
summary
>
public string Title { get; set; }
/// <
summary
>
/// Taxon filter Url
/// </
summary
>
public string Url { get; set; }
}
Construct URL
In this construct URL part of the field control logic, all items are added to a list, which is then bound to a Repeater to display them. The field name of the control are set, using the first item, since it has been checked that it exists and is also aware that all items are of the same classification. Then, the data list is bound.
First, the main page URL must be constructed. To do this, use a method that will set it once, and only returned it or each Taxa:
/// <
summary
>
/// Get the page Url using the System.Web.SiteMapProvider
/// </
summary
>
/// <
returns
>The page Url</
returns
>
protected string GetMainUrl()
{
if (string.IsNullOrWhiteSpace(MainUrl))
{
SiteMapProvider siteMap = SiteMapBase.GetCurrentProvider();
string url = string.Empty;
// The CurrentNode will be null if we are in Page's Revision History
if (siteMap.CurrentNode != null)
{
url = siteMap.CurrentNode.Url;
if (VirtualPathUtility.IsAppRelative(url))
{
// Get absolute Url
url = VirtualPathUtility.ToAbsolute(url);
}
}
this.MainUrl = url;
}
return this.MainUrl;
}
The SiteMap provider is used to get the URL. Then, the taxonUrl is generated using the Telerik.Sitefinity.Web.UrlEvaluation.TaxonomyEvaluator.
/// <
summary
>
/// Build the Taxon Url that will be used to filter the Widget displaying the content items
/// </
summary
>
/// <
param
name
=
"taxon"
>The Taxon used</
param
>
/// <
param
name
=
"taxonomyName"
>The Taxon, Taxonomy name</
param
>
/// <
returns
>The Filter Url</
returns
>
private string BuilTaxonUrl(ITaxon taxon, string taxonomyName)
{
TaxonomyEvaluator evaluator = new TaxonomyEvaluator();
TaxonBuildOptions taxonBuildOptions = TaxonBuildOptions.None;
string evaluatedResult = string.Empty;
if (taxon is HierarchicalTaxon)
{
taxonBuildOptions = TaxonBuildOptions.Hierarchical;
HierarchicalTaxon hierarchicalTaxon = taxon as HierarchicalTaxon;
evaluatedResult = evaluator.BuildUrl(taxonomyName, hierarchicalTaxon.FullUrl,
taxon.Taxonomy.TaxonName, taxonBuildOptions, this.GetUrlEvaluationMode(), null);
}
else
{
taxonBuildOptions = TaxonBuildOptions.Flat;
evaluatedResult = evaluator.BuildUrl(taxonomyName, taxon.UrlName.Value,
taxonomyName, taxonBuildOptions, this.GetUrlEvaluationMode(), null);
}
return evaluatedResult;
}
if (this.HideLinks == false) // HideLinks property default value
{
string url = GetMainUrl();
string evaluatedResult = BuilTaxonUrl(taxon, taxonomyName);
taxonUrl = string.Concat(url, evaluatedResult);
}
model.Url = taxonUrl;
Displaying the Taxa items
The following snippet is a template of the field control:
<%@ Control %>
<
div
>
<
b
><
asp:Label
ID
=
"fieldTitle"
runat
=
"server"
></
asp:Label
></
b
>
<
asp:Repeater
ID
=
"TaxaRepeater"
runat
=
"server"
>
<
ItemTemplate
>
<
a
runat
=
"server"
visible='<%# (DataBinder.Eval(Container.DataItem, "Url").Equals(String.Empty) = False)%>'
href='<%#DataBinder.Eval (Container.DataItem,"Url")%>'>
<%#DataBinder.Eval(Container.DataItem, "Title")%></
a
>
<
span
runat
=
"server"
visible='<%# (DataBinder.Eval(Container.DataItem,"Url").Equals(String.Empty)) %>'>
<%#DataBinder.Eval(Container.DataItem, "Title")%></
span
>
</
ItemTemplate
>
<
SeparatorTemplate
>
<
span
>, </
span
>
</
SeparatorTemplate
>
</
asp:Repeater
>
</
div
>
In the label above, the name of the classification are displayed, for instance ‘Tags’ or ‘Categories’. The repeater uses Item and Separator templates.
Error handling
The error handling is very simple and efficient in the field control and encompasses the whole custom logic in a try/catch statement
::
/// <
summary
>
/// Used to prepare and bind the data to the field control's controls when initializng
/// </
summary
>
/// <
param
name
=
"container"
>The container of the control</
param
>
protected override void InitializeControls(GenericContainer container)
{
try
{
if (this.GetIndexRenderMode() == Telerik.Sitefinity.Web.UI.IndexRenderModes.Normal)
{
List<
TaxonModel
> data = PrepareData();
if (data != null)
{
BindData(data);
}
}
}
catch (Exception ex)
{
// If in Backend, throw the exception again,
// it will be shown in the widget placeholder
if (this.IsBackend())
{
throw ex;
}
else
{
// Hide the field control
this.Visible = false;
// Write in error log
Log.Write(ex, ConfigurationPolicy.ErrorLog);
}
}
}