Categories
Bloggers
Blogs RSS feed

Filtering Sitefinity DynamicContentView on the frontend

by Boyan Barnev
The scenario for today's blog post is a pretty straightforward one - as a website developer I'd like to offer my frontend users a mechanism for filtering the list of Dynamic Module items by certain criteria.

To represent the idea better, let's say we'll be dealing with Offices - we're an international company that has offices around the globe. 

Handling this type of scenario with Sitefinity Module Builder takes not more than 5 minutes - the time in which I defined my new module, added some fields to specify the office Title, Country, City, ZipCode, PhoneNumber etc. fields.

Once we've created the module our next step is to deliver the filtering functionality.

For this purpose we're going to inherit from DynamicContentView, which is the type of the widget ModuleBuilder creates for displaying your new module content:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Telerik.Sitefinity.DynamicModules.Web.UI.Frontend;
 
namespace SitefinityWebApp.OfficesModule
{
    public class DynamicContentViewCustom : DynamicContentView
    {
        protected DynamicContentViewMaster MasterViewControl
        {
            get
            {
                if (this.masterViewControl == null)
                {
                    this.masterViewControl = new DynamicContentViewMasterCustom(this.DynamicManager);
                }
                return this.masterViewControl;
            }
            set
            {
                this.masterViewControl = value;
            }
        }
 
        protected override void InitializeMasterView()
        {
            if (this.HasValidRelatedDataConfiguration && this.RelatedItemsIds != null)
            {
                this.MasterViewControl.SourceItemsIds = this.RelatedItemsIds;
            }
            this.MasterViewControl.TemplateKey = string.IsNullOrEmpty(this.MasterViewDefinition.TemplateKey) ? this.DefaultMasterTemplateKey : this.MasterViewDefinition.TemplateKey;
            this.MasterViewControl.DynamicContentType = this.DynamicContentType;
            this.MasterViewControl.MasterViewDefinition = this.MasterViewDefinition;
            this.MasterViewControl.UrlEvaluationMode = this.UrlEvaluationMode;
            this.MasterViewControl.UrlKeyPrefix = this.UrlKeyPrefix;
            this.Controls.Add(this.MasterViewControl);
        }
         
        private DynamicContentViewMaster masterViewControl;
    }
}
-------------------------------------------------------------------------------------------------------------------------------
NOTE:
After Sitefinity 6.1 the DynamicContentViewMaster class now has a Host property which allows you to easily access the DynamicContentView (which is the Host) fopr its master/details view. This change requires you to modify the above code slightly, so if you are using Sitefinity 6.1 or higher version of our product, please use this sample instead:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Telerik.Sitefinity.DynamicModules.Web.UI.Frontend;
  
namespace SitefinityWebApp.OfficesModule
{
    public class DynamicContentViewCustom : DynamicContentView
    {
        protected DynamicContentViewMaster MasterViewControl
        {
            get
            {
                if (this.masterViewControl == null)
                {
                    this.masterViewControl = new DynamicContentViewMasterCustom(this.DynamicManager);
                    this.masterViewControl.Host = this;
                }
                return this.masterViewControl;
            }
            set
            {
                this.masterViewControl = value;
            }
        }
  
        protected override void InitializeMasterView()
        {
            if (this.HasValidRelatedDataConfiguration && this.RelatedItemsIds != null)
            {
                this.MasterViewControl.SourceItemsIds = this.RelatedItemsIds;
            }
            this.MasterViewControl.TemplateKey = string.IsNullOrEmpty(this.MasterViewDefinition.TemplateKey) ? this.DefaultMasterTemplateKey : this.MasterViewDefinition.TemplateKey;
            this.MasterViewControl.DynamicContentType = this.DynamicContentType;
            this.MasterViewControl.MasterViewDefinition = this.MasterViewDefinition;
            this.MasterViewControl.UrlEvaluationMode = this.UrlEvaluationMode;
            this.MasterViewControl.UrlKeyPrefix = this.UrlKeyPrefix;
            this.Controls.Add(this.MasterViewControl);
        }
          
        private DynamicContentViewMaster masterViewControl;
    }
}
-------------------------------------------------------------------------------------------------------------------------------
As you can see from the above sample, our new class overrides just one method of the base DynamicContentView, namely InitializeMasterView(). In InitializeMasterView() we are able to substitute the default MasterViewControl with our custom one - DynamicContentViewMasterCustom, defined above.

Our DynamicContentViewMasterCustom is a new class, inheriting from DynamicContentViewMaster, and will have a customized template that will facilitate the UI for filtering on the frontend.

For the sake of this example I've copied the default template our new module has created (you can access this from Design->WidgetTemplates-> YourModule list) and added the desired controls to it:
<%@ Control Language="C#" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.PublicControls.BrowseAndEdit" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.ContentUI" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.Comments" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.Fields" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="telerik" Namespace="Telerik.Web.UI" Assembly="Telerik.Web.UI" %>
 
<span>City</span>
<br />
<asp:TextBox ID="cityTextBox" runat="server" />
<br />
<span>Country</span>
<br />
<asp:TextBox ID="countryTextBox" runat="server" />
<br />
<span>ZipCode</span>
<br />
<asp:TextBox ID="zipCodeTextBox" runat="server" />
<br />
<asp:Button ID="searchBtn" Text="Search" runat="server" />
 
<telerik:RadListView ID="dynamicContentListView" ItemPlaceholderID="ItemsContainer" runat="server" EnableEmbeddedSkins="false" EnableEmbeddedBaseStylesheet="false">
    <LayoutTemplate>
        <ul class="sfitemsList sfitemsListTitleDateTmb">
            <asp:PlaceHolder ID="ItemsContainer" runat="server" />
        </ul>
    </LayoutTemplate>
    <ItemTemplate>
        <li class="sfitem sfClearfix">
            <h2 class="sfitemTitle">
                <sf:DetailsViewHyperLink ID="DetailsViewHyperLink" TextDataField="Title" runat="server" />
            </h2>
            <sf:FieldListView ID="PublicationDate" runat="server" Format="{PublicationDate.ToLocal():MMM d, yyyy, HH:mm tt}" WrapperTagName="div" WrapperTagCssClass="sfitemPublicationDate" />
        </li>
    </ItemTemplate>
</telerik:RadListView>
<sf:Pager ID="pager" runat="server"></sf:Pager>
<asp:PlaceHolder ID="socialOptionsContainer" runat="server"></asp:PlaceHolder>


In the DynamicContentViewMasterCustom we can now reference these controls, using the this.Container.GetControl<T>() approach:
#region Control References
 
      protected virtual TextBox CityTextBox
      {
          get
          {
              return this.Container.GetControl<TextBox>("cityTextBox", true);
          }
      }
 
      protected virtual TextBox CountryTextBox
      {
          get
          {
              return this.Container.GetControl<TextBox>("countryTextBox", true);
          }
      }
 
      protected virtual TextBox ZipCodeTextBox
      {
          get
          {
              return this.Container.GetControl<TextBox>("zipCodeTextBox", true);
          }
      }
 
      protected virtual Button SearchButton
      {
          get
          {
              return this.Container.GetControl<Button>("searchBtn", true);
          }
      }
 
      #endregion

and then subscribe to the Click event of the SearchButton by overriding the base InitializeControls method:
protected override void InitializeControls(Telerik.Sitefinity.Web.UI.GenericContainer container)
        {
            SearchButton.Click += SearchButton_Click;
            base.InitializeControls(container);
        }

our final step is to implement the actual filtering of the DataSource according to the user input:

void SearchButton_Click(object sender, EventArgs e)
        {
            var dataSource = ((IEnumerable<DynamicContent>)this.DynamicContentListView.DataSource);
            if (!this.CityTextBox.Text.IsNullOrEmpty())
                dataSource = dataSource.Where(itm => itm.GetValue(cityFieldName).ToString().Contains(CityTextBox.Text));
 
            if (!this.CountryTextBox.Text.IsNullOrEmpty())
                dataSource = dataSource.Where(itm => itm.GetValue(countryFieldName).ToString().Contains(CountryTextBox.Text));
                
            if (!this.ZipCodeTextBox.Text.IsNullOrEmpty())
                   dataSource = dataSource.Where(itm => itm.GetValue(zipFieldName).ToString().Contains(ZipCodeTextBox.Text));
 
            this.DynamicContentListView.DataSource = dataSource.ToList();
        }

as you can see, DynamicContentListView already has its DataSource set for us, so we can access it, and apply our filtering directly.

Once you're done the last step is registering your new widget. Go to App_Data/Sitefinity/Configuration/ToolboxesConfig.config and find the entry for your module's widget. It should look  like this:
<add enabled="True" type="Telerik.Sitefinity.DynamicModules.Web.UI.Frontend.DynamicContentView, Telerik.Sitefinity" title="OfficesCustom" cssClass="sfNewsViewIcn" moduleName="Offices" DynamicContentTypeName="Telerik.Sitefinity.DynamicTypes.Model.Offices.Office" DefaultMasterTemplateKey="3fc7803a-47fd-6966-a5dc-ff0000f47b12" DefaultDetailTemplateKey="40c7803a-47fd-6966-a5dc-ff0000f47b12" visibilityMode="None" name="Telerik.Sitefinity.DynamicTypes.Model.Offices.Office" />

Change the type parameter to match the type of your custom DynamicContentView:

<add enabled="True" type="SitefinityWebApp.OfficesModule.DynamicContentViewCustom" title="OfficesCustom" cssClass="sfNewsViewIcn" moduleName="Offices" DynamicContentTypeName="Telerik.Sitefinity.DynamicTypes.Model.Offices.Office" DefaultMasterTemplateKey="3fc7803a-47fd-6966-a5dc-ff0000f47b12" DefaultDetailTemplateKey="40c7803a-47fd-6966-a5dc-ff0000f47b12" visibilityMode="None" name="Telerik.Sitefinity.DynamicTypes.Model.Offices.Office" />

Save the file and restart your application - you'll now be able to drop the widget on any Sitefinity page and enjoy the results.

You can find the complete implementation of our DynamicContentViewMasterCustom along with the custom template and DynamicContentViewCustom attached to this post - OfficesModule.

For your convenience please find here a short video demonstrating the final results as well.

I hope you enjoyed reading about this simple functionality, and the ease of achieving it with Sitefinity. 

6 comments

Leave a comment
  1. Steve Apr 03, 2013
    This is great thanks...can't tell you how many times I've made custom search implementations via the "Code Samples"
  2. Melinda Apr 30, 2013
    Anyone else getting an error of "Type SitefinityWebApp.OfficesModule.DynamicContentViewCustom" cannot be resolved?
  3. Osman Aug 21, 2013
    Thanks for the great article. That's exactly what I needed.

    However, I cannot make it work.

    Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

    Line 74: {
    Line 75: SearchButton.Click += SearchButton_Click;
    Line 76: base.InitializeControls(container);
    Line 77: }
    Line 78:

    DynamicContentViewMasterCustom.cs    Line: 76 


    Any suggestions?
  4. Daniel Plomp Aug 22, 2013
    @Osman: to me it seems like a breaking change in Sitefinity 6.1. I also have this problem. I had this working fine within SF 5.4.

    See this forum post: http://www.sitefinity.com/developer-network/forums/bugs-issues-/bug-in-dynamic-module-override

    Or Google+: https://plus.google.com/103578452542207031837/posts/A2S9gdH3UG3
  5. OC Feb 06, 2014
    Hmmm... Having a real problem with getting this to work in 6.3. But not where expected. The problem is getting SF to select my custom template at all.

    I have copied the code from here, and have updated my TollboxesConfig with the following:

     <add enabled="True" type="Telerik.Sitefinity.DynamicModules.Web.UI.Frontend.DynamicContentView, Telerik.Sitefinity" title="Boats" cssClass="sfNewsViewIcn" moduleName="Boats" DynamicContentTypeName="Telerik.Sitefinity.DynamicTypes.Model.Boats.Boat" DefaultMasterTemplateKey="76c05baf-9703-6e9e-ab36-ff0000e54d44" DefaultDetailTemplateKey="77c05baf-9703-6e9e-ab36-ff0000e54d44" visibilityMode="None" name="Telerik.Sitefinity.DynamicTypes.Model.Boats.Boat" />
    <add enabled="True" type="SitefinityWebApp.Custom.BoatsContentView, SitefinityWebApp" title="BoatsCustom" cssClass="sfNewsViewIcn" moduleName="Boats" DynamicContentTypeName="Telerik.Sitefinity.DynamicTypes.Model.Boats.Boat" DefaultMasterTemplateKey="76c05baf-9703-6e9e-ab36-ff0000e54d44" DefaultDetailTemplateKey="77c05baf-9703-6e9e-ab36-ff0000e54d44" visibilityMode="None" name="Telerik.Sitefinity.DynamicTypes.Model.Boats.BoatCustom" />

    No matter witch widget I place on my page, the result is the same. It is showing the default DynamicContentView, and not the custom one...

    Are there any changes in 6.3 that would make this happen?

    OC
  6. OC Feb 06, 2014
    Found it :-)

    You have to remove the DefaultMasterTemplateKey  from the ToolboxesConfig in order to get this to work in 6.3!

    OC

    Leave a comment