Monday, April 23, 2012

The making of DevReach.com - Part 2, Customizing the front-end.

This is going to be a series of blog posts, that will review the process of making the latest version of DevReach.Com. For the this version we are going to use Sitefinity latest version and exploit all of its features as much as we can. We are currently working together with Sitefinity Team and producing a lot of feedback for them, part of the features that we need will probably be part of the next release of the product.

In the previous post, we created to new modules "Speakers" and "Lectures" with a relation between them. Now it's time to make use of this relation and show it to our public users. In order to do this, we need to extend the layout of the widget that Sitefinity has generated for us, once we created our custom module.

Let's start with preparing the Lectures page. For this page we want to have a list with all sessions for the current edition of DevReach, with option to filter by Track, Level and Technology. We also want to show, which speakers will take part in every single lecture, when it starts and ends, the title and the description. Eventually, we will need paging if the sessions are too many.

First, we will take care of the sessions' presentation or in order words with the master template. The best option would be to use the built-in template designer or even better the Sitefinity Thunder extension and modify the default master template. Unfortunately, we need some backend code in order to retrieve the speakers attached to the session, so we will have to use an external template.

We created a new User Control with code behind (also known as external template in Sitefinity), this control should have RadListView with ID="dynamicContentListView" and also a Pager control with ID="pager" from the built-in Sitefinity controls. These are the controls that the Sitefinity engine will try to bind the retrieved data. Finally, our ascx looks similar to this:


<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="OpenAccessDataProvider,a931ecf5cdbc454c87f646d86204ad7e.ascx.cs" Inherits="DevReach2012.Com.UserControls.Public.LecturesList" %>
<%@ 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" %>

<telerik:RadListView ID="dynamicContentListView" runat="server">
    <ItemTemplate>
        <span id="spnTrack" runat="server" />
        Level:
        <sf:FlatTaxonField runat="server"  ID="ftfLevels" DisplayMode="Read" WebServiceUrl="~/Sitefinity/Services/Taxonomies/FlatTaxon.svc" 
        AllowMultipleSelection="true" TaxonomyId="6D4775B4-771E-42A7-B810-AB539ADD421A"
        TaxonomyMetafieldName="sessionlevels" Expanded="false" ExpandText="ClickToAddTags" BindOnServer="true" />         
        <%#Eval("StartTime") %>
        <%#Eval("EndTime") %>
        <h3><%#Eval("Title") %></h3>
        <div>
            <%#Eval("Description") %>
        </div>
        Technologies:
        <sf:FlatTaxonField runat="server"  ID="ftfTechnologies" DisplayMode="Read" WebServiceUrl="~/Sitefinity/Services/Taxonomies/FlatTaxon.svc" 
        AllowMultipleSelection="true" TaxonomyId="AF2BC11B-CB47-4B2C-8336-FF076552C27B"
        TaxonomyMetafieldName="technologies" Expanded="false" ExpandText="ClickToAddTags" BindOnServer="true" />   
        Presented By:
        <div id="dvSpeakers" runat="server">
        </div>
    </ItemTemplate>
</telerik:RadListView>
<sf:Pager id="pager" runat="server"></sf:Pager>

We used the FlatTaxonField control in order to get the custom taxons applied to our sessions. This control has a lot of properties and you need to choose the right combination of them in order to display the taxonomies applied. You need the TaxonomyId, which you can get from the 'sf_taxonomies' table in your database. The WebServiceUrl is also required and is the same for all flat taxonomies. The last thing is the TaxonomyMetafieldName property which should be the same as the property name in your dynamic module. It is by default, very similar to the taxonomy name.

Now, let's take a look at the codebehind file. In order to show the speakers assigned to this session with have subscribed for the ItemDataBound event of the RadListView and retrieved the speakers using the Sitefinity API ( you can take a look at the code reference that is automatically generated for you in the back end on the Module Builder page).


   
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using DevReach2012.Com.CmsManagers;
using DevReach2012.Com.CmsManagers.PipesNFilters;
using DevReach2012.Com.CmsManagers.Taxonomies;
using Telerik.OpenAccess;
using Telerik.Sitefinity.DynamicModules.Model;
using Telerik.Sitefinity.Taxonomies.Model;
using Telerik.Web.UI;

    public partial class LecturesList : UserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            dynamicContentListView.ItemDataBound += new EventHandler<Telerik.Web.UI.RadListViewItemEventArgs>(dynamicContentListView_ItemDataBound);
        }

        void dynamicContentListView_ItemDataBound(object sender, Telerik.Web.UI.RadListViewItemEventArgs e)
        {
            if ((e.Item.ItemType == RadListViewItemType.DataItem) || (e.Item.ItemType == RadListViewItemType.AlternatingItem))
            {
                SpeakersManager speakersManager = new SpeakersManager();
                DynamicContent rawSession = (e.Item as RadListViewDataItem).DataItem as DynamicContent;
                HtmlGenericControl dvSpeakers = e.Item.FindControl("dvSpeakers") as HtmlGenericControl;
                Guid[] speakerIds = rawSession.FieldValue<Guid[]>("Speakers");
                IList<DynamicContent> speakers = speakersManager.GetByIds(speakerIds);
                foreach (DynamicContent speaker in speakers)
                {
                    HtmlAnchor linkSpeaker = new HtmlAnchor();
                    linkSpeaker.InnerText = speaker.FieldValue<string>("LastName") + ", ";
                    linkSpeaker.Attributes.Add("href", speakersManager.GetDefaultUrl(speaker));
                    dvSpeakers.Controls.Add(linkSpeaker);
                }
            }
        }
    }
SpeakersManager is a class that we defined in order to wrap all the work with the Sitefinity API regarding Speakers module in common place. We have similar manager for all other dynamic modules and all of them derive from a common base class.

 BaseDynamicContentManager.cs
    public abstract class BaseDynamicContentManager
    {
        public BaseDynamicContentManager(string dynamicTypeName)
     {
                this.dynamicTypeName = dynamicTypeName;
     }

        public IQueryable<dynamiccontent&rt; GetAllPublished()
        {
            DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager();
            Type speakerType = TypeResolutionService.ResolveType(dynamicTypeName);

            IQueryable<dynamiccontent&rt; speakers = dynamicModuleManager
            .GetDataItems(speakerType)
            .Where(s => s.Status == Telerik.Sitefinity.GenericContent.Model.ContentLifecycleStatus.Live && s.Visible);

            return speakers;
        }

        protected readonly string dynamicTypeName;
    }


SpeakersManager.cs
    public class SpeakersManager : BaseDynamicContentManager
    {
        public SpeakersManager() : base("Telerik.Sitefinity.DynamicTypes.Model.Speakers.Speaker")
        {
        }

        internal IList<dynamiccontent&;rt; GetByIds(Guid[] speakerIds)
        {
            DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager();
            Type speakerType = TypeResolutionService.ResolveType(dynamicTypeName);
            IList<dynamiccontent&rt; speakers = new List<dynamiccontent&rt;();

            foreach (Guid speakerGuid in speakerIds)
            {
                speakers.Add(dynamicModuleManager.GetItem(speakerType, speakerGuid) as DynamicContent);
            }

            return speakers;
        }

        internal string GetDefaultUrl(DynamicContent speaker)
        {
            foreach (DynamicContentUrlData url in speaker.Urls)
            {
                if (url.IsDefault)
                    return url.Url;
            }

            return speaker.Urls.FirstOrDefault().Url;
        }
    }
We try to have as little as possible hard-coded strings like types, etc. That's why we keep this strings as properties of the managers and use them wherever we need them. So far, we have a nice list with all the sessions and the properties we need. The beauty of this approach is that Sitefinity will take care for all the data retrieving, paging and filtering and all we do is to define the layout ( and pullout the Speakers which will Sitefinity cannot handle).

The last thing we need to do is to tell Sitefinity to use this user control as a template instead of the built-in one. Now, the things get little hacky here. Drag and drop the widget that the module builder has created for you, in our case it's called sessions and click 'Edit' and get the DefaultMasterTemplateId which is basically a Guid. Then, rename you user control in the following manner OpenAccessDataProvider,.ascx You might need to change the OpenAccessDataProvider to another one if you not using it as a page provider, but I doubt this is the case. The last thing is to move this control (with the codebehind) to a folder under the root of you site called 'SfCtrlPresentation'. If you have read careful, the user control that I showed you follows this convention. This is pretty inconvenient approach and I guess that this will be greatly improved in some of the following releases - probably by allowing us to set the user control with a template path or even better - by using Sitefinity Thunder.

Now it's time to deal with the filtering. We will use the standard Tags control, that is in the Classification category. By default, it shows the Tags taxonomy that we do not use currently. We need to set some properties to it. Drag and drop the control, click Edit and set the TaxonomyId property ( you can get it from the database table). Set the FieldName property to the name of the property from the dynamic module. This way we get a list with all taxons from this taxonomy and we can filter the sessions list with a single link on a taxon. This works by passing filtering parameters through the url. We can drag and drop similar widget for all taxonomies that we need in set it up in the same manner. In our case we have on control for filtering by technologies, by session level and by track. If we want this to look even better we can add some jquery and transform this links to dropdowns. Here is an example how to do this.

The speakers page is even easier as we need only a part of the things we already did with the sessions. So far, we managed to create our custom data modules and present them in convenient way to our users. What's next is to setup our eCommerce module and use it to sell passes for our conference. I hope this is useful to you, stay tuned and don't miss the next series.
RN4BQH8V23SG