Create a Custom Designer with a Categories Filter - Front- & Back-End Development - Front- & Back-End Development - Progress Community
 Front- & Back-End Development

Create a Custom Designer with a Categories Filter

  • Create a Custom Designer with a Categories Filter
  • I am creating a custom designer for a widget that will display News content items among other things.

    I would like to allow filtering of News content items by Category (as the normal News widget does).  Is there any documentation describing how to do this?

    So far I've identified some code to put in the designer template:

    <%@ Register Assembly="Telerik.Sitefinity" TagPrefix="sitefinity" Namespace="Telerik.Sitefinity.Web.UI" %>
    <%@ Register Assembly="Telerik.Sitefinity" TagPrefix="designers" Namespace="Telerik.Sitefinity.Web.UI.ControlDesign" %>
    <%@ Register Assembly="Telerik.Sitefinity" TagPrefix="sfFields" Namespace="Telerik.Sitefinity.Web.UI.Fields" %>
     
    <sfFields:FormManager id="formManager" runat="server" />
     
    <div>
        <h2>
            <asp:Literal ID="choicesTitle" runat="server" Text="<%$Resources:Labels, WhichNewsToDisplay %>" /></h2>
        <ul class="sfRadioList">
            <li>
                <asp:RadioButton runat="server" ID="contentSelect_AllItems" Checked="true" GroupName="ContentSelection"
                    Text="<%$Resources:Labels, AllPublishedNews %>" />
            </li>
            <li>
                <asp:RadioButton runat="server" ID="contentSelect_SimpleFilter" GroupName="ContentSelection"
                    Text="<%$Resources:Labels, SelectionOfNews %>" />
                <div id="selectorsPanel">
                    <designers:FilterSelector ID="filterSelector" runat="server" AllowMultipleSelection="true"
                        ItemsContainerTag="ul" ItemTag="li" ItemsContainerCssClass="sfCheckListBox sfExpandedPropertyDetails"
                        DisabledTextCssClass="sfTooltip">
                        <Items>
                            <designers:FilterSelectorItem ID="FilterSelectorItem1" runat="server" Text="<%$Resources:Labels, ByCategories %>"
                                GroupLogicalOperator="AND" ItemLogicalOperator="OR" ConditionOperator="Contains"
                                QueryDataName="Categories" QueryFieldName="Category" QueryFieldType="System.Guid">
                                <SelectorResultView>
                                    <sitefinity:HierarchicalTaxonSelectorResultView ID="HierarchicalTaxonSelectorResultView1"
                                        runat="server" WebServiceUrl="~/Sitefinity/Services/Taxonomies/HierarchicalTaxon.svc"
                                        AllowMultipleSelection="true">
                                    </sitefinity:HierarchicalTaxonSelectorResultView>
                                </SelectorResultView>
                            </designers:FilterSelectorItem>
                            <designers:FilterSelectorItem ID="FilterSelectorItem2" runat="server" Text="<%$Resources:Labels, ByTags %>"
                                GroupLogicalOperator="AND" ItemLogicalOperator="OR" ConditionOperator="Contains"
                                QueryDataName="Tags" QueryFieldName="Tags" QueryFieldType="System.Guid">
                                <SelectorResultView>
                                    <sitefinity:FlatTaxonSelectorResultView ID="FlatTaxonSelectorResultView1" runat="server"
                                        WebServiceUrl="~/Sitefinity/Services/Taxonomies/FlatTaxon.svc" AllowMultipleSelection="true">
                                    </sitefinity:FlatTaxonSelectorResultView>
                                </SelectorResultView>
                            </designers:FilterSelectorItem>
                        </Items>
                    </designers:FilterSelector>
                </div>
            </li>
            <li style="display: none;">
                <asp:RadioButton runat="server" Enabled="false" ID="contentSelect_AdvancedFilter"
                    GroupName="ContentSelection" Text="<%$Resources:Labels, AdvancedSelection %>" /><asp:Literal
                        ID="Literal1" runat="server" Text="<%$Resources:Labels, InProcessOfImplementation %>" />
            </li>
        </ul>
    </div>

    Some specific issues I need to resolve:
    How do I configure the 'FilterSelectorItem' control so that the 'Select a category' dialog displays a list of categories to choose from?
    How do I save the option values for the widget instance?  Do I define a ContentSelection property and Category property for the widget?
  • Hi Antoine,

    You can modify the control as shown below ( only for categories)

    <designers:FilterSelector ID="filterSelector" runat="server" AllowMultipleSelection="true"
                        ItemsContainerTag="ul" ItemTag="li" ItemsContainerCssClass="sfCheckListBox sfExpandedPropertyDetails" DisabledTextCssClass="sfTooltip">
                        <Items>
                            <designers:FilterSelectorItem ID="FilterSelectorItem1" runat="server" Text="<%$Resources:Labels, ByCategories %>"
                                GroupLogicalOperator="AND" ItemLogicalOperator="OR" ConditionOperator="Contains"
                                QueryDataName="Categories" QueryFieldName="Category" QueryFieldType="System.Guid">
                                <SelectorResultView>
                                    <sitefinity:HierarchicalTaxonSelectorResultView ID="HierarchicalTaxonSelectorResultView1" runat="server" WebServiceUrl="~/Sitefinity/Services/Taxonomies/HierarchicalTaxon.svc"
                                        AllowMultipleSelection="true">
                                    </sitefinity:HierarchicalTaxonSelectorResultView>
                                </SelectorResultView>
                            </designers:FilterSelectorItem>
                        </Items>
    </designers:FilterSelector>

    Inside your control designer you should make a reference to the control

    protected virtual FilterSelector filterSelector
          
              get
              
                  return this.Container.GetControl<FilterSelector>("filterSelector", true);
              
          


    and cal SetTaxonomyID method inside InitializeControls

    filterSelector.SetTaxonomyId("Categories", TaxonomyManager.CategoriesTaxonomyId);

    The selected value should be returned as an additional field on the client.

    Greetings,
    Ivan Dimitrov
    the Telerik team
    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get now >>
  • Thank you Ivan.  I put the code you instructed me to add in my ControlDesignerBase subclass and everything is working.
  • In a related issue, I am trying to get the FlatTaxonField control working.

    Eventually I would like to set it up with a new flat classification that I created, but for now, I am just trying to get it to work with the out-of-the-box Tags classification.

    In my custom designer .aspx, I have the following:
    <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TabbedDocumentCollectionDesignerTemplate.ascx.cs"
        Inherits="SitefinityWebApp.Widgets.TabbedDocumentCollection.TabbedDocumentCollectionDesignerTemplate" %>
    <%@ Register Assembly="Telerik.Sitefinity" TagPrefix="sitefinity" Namespace="Telerik.Sitefinity.Web.UI" %>
    <%@ Register Assembly="Telerik.Sitefinity" TagPrefix="designers" Namespace="Telerik.Sitefinity.Web.UI.ControlDesign" %>
    <%@ Register Assembly="Telerik.Sitefinity" TagPrefix="sfFields" Namespace="Telerik.Sitefinity.Web.UI.Fields" %>
     
    <sfFields:FormManager ID="formManager" runat="server" />
     
    <div class="sfContentViews sfSingleContentView">
        <div class="sfTxtFieldCtrl">
            <sfFields:FlatTaxonField ID="fieldDocumentCollection"
                                     DisplayMode="Read"
                                     BindOnServer="true"
                                     runat="server"
                                     WebServiceUrl="~/Sitefinity/Services/Taxonomies/FlatTaxon.svc"
                                     Expanded="true"
                                     TaxonomyMetafieldName="DocumentCollectionsForDisplay"
                                     Title="Document Collections"
                                     HideWhenNoTaxaFound="false"
                                     CssClass="sfpostTagsWrp" />
        </div>
    </div>

    In the javascript designer file, I have the following:
    Type.registerNamespace("SitefinityWebApp.Widgets.TabbedDocumentCollection");
     
    SitefinityWebApp.Widgets.TabbedDocumentCollection.TabbedDocumentCollectionDesigner = function (element)
        SitefinityWebApp.Widgets.TabbedDocumentCollection.TabbedDocumentCollectionDesigner.initializeBase(this, [element]);
     
    SitefinityWebApp.Widgets.TabbedDocumentCollection.TabbedDocumentCollectionDesigner.prototype =
        initialize: function ()
            SitefinityWebApp.Widgets.TabbedDocumentCollection.TabbedDocumentCollectionDesigner.callBaseMethod(this, 'initialize');
     
        ,
        dispose: function ()
            SitefinityWebApp.Widgets.TabbedDocumentCollection.TabbedDocumentCollectionDesigner.callBaseMethod(this, 'dispose');
        ,
        refreshUI: function ()
            var data = this._propertyEditor.get_control();
            jQuery("#fieldDocumentCollection").val(data.DocumentCollectionsForDisplay);
        ,
        applyChanges: function ()
            var controlData = this._propertyEditor.get_control();
            controlData.DocumentCollectionsForDisplay = jQuery("#fieldDocumentCollection").val();
        
     
    SitefinityWebApp.Widgets.TabbedDocumentCollection.TabbedDocumentCollectionDesigner.registerClass('SitefinityWebApp.Widgets.TabbedDocumentCollection.TabbedDocumentCollectionDesigner',
                                                                     Telerik.Sitefinity.Web.UI.ControlDesign.ControlDesignerBase);
    if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

    In my ControlDesignerBase subclass, I have added:
    protected override void InitializeControls(Telerik.Sitefinity.Web.UI.GenericContainer container)
        base.DesignerMode = ControlDesignerModes.Simple;
     
        TaxonomyManager taxonomyManager = TaxonomyManager.GetManager();
        FlatTaxonomy taxonomyDocumentCollection = taxonomyManager.GetTaxonomies<FlatTaxonomy>().Where(t => t.Name == "Tags").SingleOrDefault();
     
        this.DocumentCollectionsField.TaxonomyId = taxonomyDocumentCollection.Id;
     
     
    protected virtual FlatTaxonField DocumentCollectionsField
        get
        
            return this.Container.GetControl<FlatTaxonField>("fieldDocumentCollection", true);
        

    When I run the custom designer, the FlatTaxonField title is displayed, but no categories are displayed.  Is there a step(s) in initializing the control that I am missing?

    Also, I want the user's classification selection to be saved to the widget instance.  If I understand correctly, the 'TaxonomyMetafieldName' property of the FlatTaxonField specified the widget instance property that the classification selections are loaded from and saved to.  Is that correct?  What should the datatype of this property be in the widget?

    Thanks!
    Antoine
  • Hi Antoine,

    You need to supply TaxonomyId . Currently the control calls GetTaxonomy but you do not pass any value there, so it should be bound properly.

    Greetings,
    Ivan Dimitrov
    the Telerik team
    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get now >>
  • Hello Ivan

    I am setting the TaxonomyId in InitializeControls of my ControlDesignerBase:

    TaxonomyManager taxonomyManager = TaxonomyManager.GetManager();
    FlatTaxonomy taxonomyDocumentCollection = taxonomyManager.GetTaxonomies<FlatTaxonomy>()
                                                .Where(t => t.Name == "Tags")
                                                .SingleOrDefault();
     
    this.DocumentCollectionsField.TaxonomyId = taxonomyDocumentCollection.Id;

    Is there some other way I should bind the property?

    Antoine
  • Hello Antoine,

    This looks fine. Do you get the control populated with data? In most of the places we set the property in the template directly.

    Greetings,
    Ivan Dimitrov
    the Telerik team
    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get now >>
  • Hello guys,

    I don't find the answer for the original question : "How do I save the option values for the widget instance?  Do I define a ContentSelection property and Category property for the widget?".

    I'm able to display and select taxonomies (Tags/Categories) but in the javascript of my Desginer :
    - I need to retrieve the tags/categories selected in the FilterSelector (applyChanges function) but I don't know how
    - I need to set FilterSelector with the tags/categories that were selected before (refreshUI function).

    Can somenone help me with this?

    Thanks a lot for your help.

    Jonathan.
  • Hi Jonathan

    For various reasons, I dropped this task and thus never got it working.  If you are able to, I would be glad to see your solution in this forum post.

    Thanks,
    Antoine
  • I have the same issues... I can't figure out how to get to pass the values back and forth between my control and the designer using applyChanges and refreshUI...
  • Hi All,

    I opened a ticket about this issue and here's the answer I got from the support.

    Hi Jonathan,

    Thank you for the patience.I have explored several options for achieving this functionality, please find below my recommendations:

    1. I'd recommend you to switch the implementation from FilterSelector to ChoiceField - you can customize the frontend representation by specifying myControl.RenderChoicesAs = Telerik.Sitefinity.Web.UI.Fields.Enums.RenderChoicesAs.(specify desired view)
    An example of CHoice field implementation in a control designer can be found in the latest release of our SDK in the TaxonomyDropDown of the Products Catalog module. 

    You can define the ChoiceField in your template as:

    <%@ Control Language="C#" %>
    <%@ Register Assembly="Telerik.Sitefinity" TagPrefix="sitefinity" Namespace="Telerik.Sitefinity.Web.UI" %>
    <%@ Register Assembly="Telerik.Web.UI, Version=2011.1.413.40" Namespace="Telerik.Web.UI" TagPrefix="telerik" %>
    <%@ Register Assembly="Telerik.Sitefinity, Version=4.1.1395.0" Namespace="Telerik.Sitefinity.Web.UI.Fields" TagPrefix="sfFields" %>
      
      
    <ul class="sfRadioList sfTitledList">
      <li>
         <sfFields:ChoiceField ID="hidePrice" runat="server"
                             CssClass="sfInlineWrapper"
                             DataFieldName="HidePriceOnFrontend"
                             DisplayMode="Write"
                             RenderChoicesAs="SingleCheckBox">
       
     
        </sfFields:ChoiceField>
     </li>
    </ul>

    and then populate the Choices in the codefile by getting all Taxa with the API:

    protected override void InitializeControls(GenericContainer container)
                                
               var tManager = TaxonomyManager.GetManager();
             //  var tid = tdefinition.TaxonomyId;
               var allTags = tManager.GetTaxa<FlatTaxon>(); //.GetTaxonomies<FlatTaxonomy>();
               if (allTags != null)
               
                   var myControl = this.Container.GetControl<ChoiceField>("hidePrice", true);
                   myControl.RenderChoicesAs = Telerik.Sitefinity.Web.UI.Fields.Enums.RenderChoicesAs.CheckBoxes; //
                   // or you can use Telerik.Sitefinity.Web.UI.Fields.Enums.RenderChoicesAs.CheckBoxes for multiple choice
                   myControl.Choices.Clear();
                   foreach (var taxon in allTags)
                   
                       var choice = new ChoiceItem();
                       choice.Value = taxon.Id.ToString();
                       choice.Text = taxon.Title;
                       choice.Enabled = true;
                       myControl.Choices.Add(choice);
                   
               
           

    please note to add the copmponent ScriptDescriptors:

    public override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
            
                var descriptor = new ScriptControlDescriptor(typeof(DatePickerDesignerView).FullName, this.ClientID);
                descriptor.AddComponentProperty("minimumPicker", this.MinimumPicker.ClientID);
                descriptor.AddComponentProperty("maximumPicker", this.MaximumPicker.ClientID);
                descriptor.AddComponentProperty("hidePrice", this.Container.GetControl<ChoiceField>("hidePrice", true).ClientID);
                  
      
                return new[] descriptor ;
            

    I believe this implementation would be much easier. Concerning your inquiries about saving and loading the selection of tags, I'd suggest you examine the ChoiceField implementation in the products catalog sample, where a public property is exposed in your control and you use controlData object to persist the selection.
    For example:


    public Guid[] MyTagSelection
            
                get
                
                    if(this.myTagSelection == null)
                        this.myTagSelection = new Guid[];
                    return this.myTagSelection;
                
                set
                
                    this.myTagSelection = value;
                
            
      
            private Guid[] myTagSelection;

    and the controlData implementation:

    refreshUI: function ()
            debugger;
            var controlData = this.get_controlData();
             var myTagSel = controlData.MyTagSelection;
            this.set_hidePrice(myTagSel);
        ,
      
        // implementation of IDesignerViewControl: forces the designer view to apply the changes on UI to the control Data
        applyChanges: function ()
            var controlData = this.get_controlData();     
            debugger;      
            var tagSelection = this._hidePrice.get_value();
            controlData.MyTagSelection = tagSelection;
        ,


    Voila!

    I know that it's just a workaround but it worked for me.

    Good luck with your dev!

    Jonathan.
  • I've gotten the FilterSelector to work for categories successfully in a control designer(thanks to a little help from inspecting the Sitefinity libraries with JustDecompile).  Start out as Ivan specified in his first post above.  In the control designer CS file you will also need to register the ScriptDescriptor for the filter selector:
    public override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        var baseDescriptors = new List<ScriptDescriptor>(base.GetScriptDescriptors());
        var newDescriptors = (ScriptControlDescriptor)baseDescriptors.Last();
        newDescriptors.AddComponentProperty("filterSelector", this.filterSelector.ClientID);
        return baseDescriptors;

    Then, for the methods in the control designer JS file:
    get_filterSelector: function ()
            return this._filterSelector;
    ,
    set_filterSelector: function (value)
        this._filterSelector = value;
    ,
    refreshUI: function ()
        var controlData = this.get_controlData();
        var additionalFilters = this.get_controlData().CategoryJSON;
        if (additionalFilters)
            additionalFilters = Sys.Serialization.JavaScriptSerializer.deserialize(additionalFilters);
        this.get_filterSelector().set_queryData(additionalFilters);
    ,
    applyChanges: function ()
        var controlData = this.get_controlData();
     
        var filterSelector = this.get_filterSelector();
        filterSelector.applyChanges();
     
        var queryData = filterSelector.get_queryData();
        if (queryData.QueryItems && queryData.QueryItems.length > 0)
            queryData = Telerik.Sitefinity.JSON.stringify(queryData);
        
        else
            queryData = null;
        
        controlData.CategoryJSON = queryData;
    ,

    The values of the FilterSelector control are now set up to be persisted, but we need to make these accessible on the widget being designed.  On the widget being designed, I added the following properties:
    // Technically, this variable would hold any of the filter data returned from the FilterSelector,
    // but it is only configured  for Categories, so I'm naming the variable as such
    public string CategoryJSON get; set;
     
    protected List<Guid> Categories
        get
        
            List<Guid> categoryGUIDList = new List<Guid>();
            if (!string.IsNullOrEmpty(CategoryJSON))
            
                //Parse the JSON string and extract the category IDs
                QueryData qData = (new JavaScriptSerializer()).Deserialize<QueryData>(CategoryJSON);
                List<QueryItem> categoryNames = qData.QueryItems.Where(qi => qi.Condition != null && qi.Condition.FieldName == "Category").ToList<QueryItem>();
                foreach (QueryItem qi in categoryNames)
                
                    categoryGUIDList.Add(Guid.Parse(qi.Value));
                
            
            return categoryGUIDList;
        

    You will also need to include the following namespaces in your widget class:
    using Telerik.Sitefinity.Web.Model;
    using System.Web.Script.Serialization;

    Now the categories selected in the designer can be accessed as a nice List<Guid>  in your custom widget. Enjoy!

    Alan
  • Hi,

    Thank you, Alan for sharing your solution.

    Regards,
    Stefani Tacheva
    Telerik
    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.