This post is the 2nd part of a 2 part blog post that focuses on how to create Designers using Sitefinity Thunder. Please refer to this blog post to see the first part...
Open up your custom Widget UI file that we created at the beginning of this post, make sure it’s NOT the Designer UI, and add the following controls to it:
<div> <p> <strong>Categories:</strong> <asp:Repeater ID="CategoriesRepeater" runat="server"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate> <li><%# Container.DataItem %></li> </ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater> </p></div><div> <p> <strong>Tags:</strong> <asp:Repeater ID="TagsRepeater" runat="server"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate> <li><%# Container.DataItem %></li> </ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater> </p></div>Add the following properties to your Widget’s code-behind file:
/// <summary>/// guid array from designer for our selected tags/// </summary>public Guid[] selectedTags;/// <summary>/// Gets or sets the selected tags./// </summary>public Guid[] SelectedTags{ get { if (selectedTags == null) selectedTags = new Guid[] { }; return selectedTags; } set { selectedTags = value; }}/// <summary>/// guid array from designer for our selected categories/// </summary>public Guid[] selectedCategories;/// <summary>/// Gets or sets the selected categories./// </summary>/// <value>/// The selected categories./// </value>public Guid[] SelectedCategories{ get { if (selectedCategories == null) selectedCategories = new Guid[] { }; return selectedCategories; } set { selectedCategories = value; }}/// <summary>/// Intermediary property for passing Tags to and from the designer/// </summary>/// <value>/// The tag value as a comma delimited string./// </value>public string TagValue{ get { return string.Join(",", SelectedTags); } set { var list = new List<Guid>(); if (value != null) { var guids = value.Split(','); foreach (var guid in guids) { Guid newGuid; if (Guid.TryParse(guid, out newGuid)) list.Add(newGuid); } } SelectedTags = list.ToArray(); }}/// <summary>/// Intermediary property for passing categories to and from the designer/// </summary>/// <value>/// The category value as a comma-delimited string./// </value>public string CategoryValue{ get { return string.Join(",", SelectedCategories); } set { var list = new List<Guid>(); if (value != null) { var guids = value.Split(','); foreach (var guid in guids) { Guid newGuid; if (Guid.TryParse(guid, out newGuid)) list.Add(newGuid); } } SelectedCategories = list.ToArray(); }}Add the following Control References to your Widget’s code-behind file:
/// <summary>/// Reference to the Label control that shows the Message./// </summary>protected virtual Repeater TagsRepeater{ get { return this.Container.GetControl<Repeater>("TagsRepeater", true); }}/// <summary>/// Reference to the Label control that shows the Message./// </summary>protected virtual Repeater CategoriesRepeater{ get { return this.Container.GetControl<Repeater>("CategoriesRepeater", true); }}Add the following method calls to your Widget’s code-behind InitializeControls method:
BindTags();
BindCategories();
Add the following two methods to your Widget’s code-behind:
/// <summary>/// Binds the selected tags to your tags control/// </summary>private void BindTags(){ // retrieve selected tags var tags = new List<string>(); foreach (var tagID in SelectedTags) { // get tag name var tag = taxMgr.GetTaxon(tagID); if (tag == null) continue; tags.Add(tag.Name); } // bind tag names TagsRepeater.DataSource = tags; TagsRepeater.DataBind();}/// <summary>/// Binds the selected tags to your categories control/// </summary>private void BindCategories(){ // retrieve selected categories var tags = new List<string>(); foreach (var tagID in SelectedCategories) { // get category name var tag = taxMgr.GetTaxon(tagID); if (tag == null) continue; tags.Add(tag.Name); } // bind category names CategoriesRepeater.DataSource = tags; CategoriesRepeater.DataBind();}
Adding Categories and Tags to your Designer
This is the first time we will be touching the Designer code as Thunder took care of that for us previously. It’s a little different, because it has different responsibilities, and has an associated javascript file, but in the end it’s just a custom Widget.
Open your custom Designer’s UI file and add the following controls to it:
<%-- Tag section --%> <li> <sitefinity:FlatTaxonField ID="TagsSelector" runat="server" DisplayMode="Write" WebServiceUrl="~/Sitefinity/Services/Taxonomies/FlatTaxon.svc/cb0f3a19-a211-48a7-88ec-77495c0f5374" TaxonomyMetafieldName="Tags" AllowMultipleSelection="true" Expanded="false" Title="Tags" /> </li> <li class="sfFormCtrl"> <asp:HiddenField ID="TagValue" runat="server" /> </li> <%-- Category section --%> <li> <label class="sfTxtLbl">Category:</label> <sitefinity:HierarchicalTaxonField ID="CategoriesSelector" runat="server" DisplayMode="Write" Expanded="false" ExpandText="ClickToAddCategories" ShowDoneSelectingButton="true" AllowMultipleSelection="true" BindOnServer="false" TaxonomyMetafieldName="Category" WebServiceUrl="~/Sitefinity/Services/Taxonomies/HierarchicalTaxon.svc" /> </li> <li class="sfFormCtrl"> <asp:HiddenField ID="CategoryValue" runat="server" /> </li>Make sure you have the following Using statements to your Designer’s code-behind file:
Now add the following Control References to the Designer’s code-behind:
/// <summary> /// Gets the control that is bound to the TagValue property /// </summary> protected virtual Control TagValue { get { return this.Container.GetControl<Control>("TagValue", true); } } /// <summary> /// Gets the tag selector control /// </summary> protected FlatTaxonField TagsSelector { get { return Container.GetControl<FlatTaxonField>("TagsSelector", true); } } /// <summary> /// Gets the control that is bound to the CategoryValue property /// </summary> protected virtual Control CategoryValue { get { return this.Container.GetControl<Control>("CategoryValue", true); } } /// <summary> /// Gets the category selector control /// </summary> protected HierarchicalTaxonField CategoriesSelector { get { return Container.GetControl<HierarchicalTaxonField>("CategoriesSelector", true); } }Now add two method calls to your Designer’s code-behind InitializeControls method:
// initialize the taxonomy selectorsCategoriesSelector.TaxonomyId = TaxonomyManager.CategoriesTaxonomyId;TagsSelector.TaxonomyId = TaxonomyManager.TagsTaxonomyId;Now add the following component and element property descriptors within the IScriptControl implementation Region:
descriptor.AddElementProperty("tagValue", this.TagValue.ClientID);descriptor.AddComponentProperty("TagsSelector", this.TagsSelector.ClientID);descriptor.AddElementProperty("categoryValue", this.CategoryValue.ClientID);descriptor.AddComponentProperty("CategoriesSelector", this.CategoriesSelector.ClientID);That’s it for the Designer UI and code-behind file. Now we’ll start editing the JavaScript file.
Add the following JavaScript variables:
this._TagsSelector = null;this._tagValue = null;this._CategoriesSelector = null;this._categoryValue = null;
Add the following items to the bottom of the refreshUI function:
/* RefreshUI TagValue */jQuery(this.get_tagValue()).val(controlData.TagValue);/* RefreshUI CategoryValue */jQuery(this.get_categoryValue()).val(controlData.CategoryValue);// load tagsvar t = this.get_TagsSelector();var tags = controlData.TagValue;if (tags != null) t.set_value(controlData.TagValue.split(","));// load categoriesvar c = this.get_CategoriesSelector();var cats = controlData.CategoryValue;if (cats != null) c.set_value(controlData.CategoryValue.split(","));Now add the following items to the bottom of the applyChanges function:
/* ApplyChanges SelectedPageID *//* ApplyChanges TagValue */controlData.TagValue = jQuery(this.get_tagValue()).val();/* ApplyChanges CategoryValue */controlData.CategoryValue = jQuery(this.get_categoryValue()).val();// save selected tagsvar t = this.get_TagsSelector();var tags = t.get_value();if (tags != null) controlData.TagValue = t.get_value().join();// save selected categoriesvar c = this.get_CategoriesSelector();var cats = c.get_value();if (cats != null) controlData.CategoryValue = c.get_value().join();Now add the following properties to the existing Properties section:
/* TagValue properties */get_tagValue: function () { return this._tagValue; },set_tagValue: function (value) { this._tagValue = value; },/* CategoryValue properties */get_categoryValue: function () { return this._categoryValue; },set_categoryValue: function (value) { this._categoryValue = value; },// Categories Selectorget_CategoriesSelector: function () { return this._CategoriesSelector; },set_CategoriesSelector: function (value) { this._CategoriesSelector = value; },// Tags Selectorget_TagsSelector: function () { return this._TagsSelector; },set_TagsSelector: function (value) { this._TagsSelector = value; }Compile your project one final time and use to your heart’s content!
Summary
Hopefully everyone found this a useful exercise and this sheds more insight into how Designers are made and what’s going on under the hood. Once you see the patterns at play here you will no doubt be able to apply your own custom controls and functionality to your Sitefinity Designers.
Though Designers aren’t necessary in all instances when creating custom widgets, they can be a very powerful tool for your business users, because, once setup, they allow the business user to customize their content display properties without the need of a developer. And as we all know a happy business user makes for a happy developer!
Sample Code
Please go to my Github Repository to download the sample project.