Custom Javascript Banner Rotator – Part 2: jQuery!

As I suggested in part 1 of this series, I recently discovered the wonderful world of jQuery, the “write less, do more javascript library”. And sure enough, this is one tool that really lives up to its name! Not only can you do all sorts of neat (even useful!) javascript effects and events, but they are suprisingly easy to do! I had put learning this on the backburner a while back, but after publishing my Banner Rotator, I figured it was about time to bring that sucker front and center and start cooking!

The banner rotator script I posted yesterday presented the perfect opportunity to jump in. Using this incredible framework, not only was I able to wrap the functionality into a plugin, but I was able to improve and refactor it to make it reusable (which is a good thing because I’m going to be using it on the city’s Convention Center website). It turned out to be so easy, I don’t even have to waste time with a back story. Let’s go straight to the code!

First thing you want to do when making a jQuery plugin is create the function and assign it to jQuery. I’ve also included the (improved!) local variables from last time to keep track of where we are in the rotation.

.

var imgSrcs = [];
var curSrc = 0;

var imgElems;
var curElem = 0;

var fadeSpeed;
var imageTimeout;

function img(src, alt){
this.src = src;
this.alt = alt;
}

jQuery.fn.bannerswapper = function()
{};

Notice that I’ve created a class img to hold the image data, including source and alternate text attributes.

Now we want to provide a way for users to supply arguments for imageTimeout and fadeSpeed. In addition, this time, we’re going to be reading from an XML file which contains the list of images (along with their alt tags!) to build our list. Handling paramters is pretty straightforward:

jQuery.fn.bannerswapper = function(xmlFile, settings)
{
var banner = this;
settings = jQuery.extend({
fade: 6000,
timeout: 10000
}, settings);
}
fadeSpeed = settings.fade;
imageTimeout = settings.timeout;

the jQuery.extend allows us to declare the settings variables, including default values. In this case, fade is set to 6000ms, and timeout (time between frames) is 10000. If users supply parameters (as we’ll see later) then these default values will be replaced. Also note that because xmlFile is explicitly defined in the function signature, it is now a REQUIRED variable.

Next, we want to read the xml file, and create our array of elements. I cannot emphasize in words just how easy this is to do with jQuery, so I’ll let the code speak for itself:

// load image sources
$.get(xmlFile, function(xml)
{
var i = 0;
$(xml).find('image').each(function()
{
imgSrcs[i++] = new img($(this).attr('src'), $(this).attr('alt'));
});

// make sure there are at least 2 elements
if (imgSrcs.length < 2) return;

// only create element if it's not already there
if (banner.length == 1) banner.append(document.createElement('img'));

// get array of image elements to swap
imgElems = $('img', banner);

// start toggling!
toggle();
});

That is literally ALL there is to it! I create a new instance of the img class for each element in the xml file and append it to the array. Once the array is complete, we just call toggle and we’re on our way!

One thing I did discover was that my previous example did not handle situations where there are an ODD number of images. So below is an improved method which toggles the image back and forth, taking this into account:

function toggle()
{
// move to next image
if (++curSrc >= imgSrcs.length) curSrc = 0;

// grab ref to elements for updating
var frontImg = imgElems[curElem];
if (++curElem > 1) curElem = 0;
var backImg = imgElems[curElem];

// set current image to hide next
frontImg.className = "";
frontImg.removeAttribute('style');

// prepare to swap image
backImg.className = "show";
backImg.src = imgSrcs[curSrc].src;
backImg.alt = imgSrcs[curSrc].alt;

// fade in next image and repeat
setTimeout(function() { $('.show').fadeIn(fadeSpeed, toggle); }, imageTimeout);
}

Can you believe it’s really that easy? Now all we have to do is call our plugin, here is the syntax.

<asp:ScriptManagerProxy ID="ScriptManager" runat="server">
<
Scripts>
<
asp:ScriptReference Path="/files/scripts/jquery/jquery-1.2.6.pack.js" />
<
asp:ScriptReference Path="/files/scripts/jquery/bannerswapper/jquery.BannerSwapper.js" />
</
Scripts>
</
asp:ScriptManagerProxy>
<script type="text/javascript">
$().ready(function()
{
$('#banner_container').bannerswapper('/files/scripts/jquery/images.xml');
});
</script>
<
div id="banner_container">
<
img src="/images/banners/banner10.jpg" alt="" />
</
div>

Also note that I’ve moved all of the stylings to an external sheet so that needs to be included in your page header as well.

I’ve packed all the files I used (excluding the images) into a zip which I’ve uploaded to myBloop. If you’d like to take a look at the full source, please use the link below. Your comments are appreciated!

Download Banner Swapper jQuery Plugin

Custom Javascript Banner Rotator – Part 1

These days, a feature that city websites should never be without is the banner rotator, that is, an image up at the top of the site that cycles through various featured photos. The City of McAllen naturally was no exception, as you can see for yourself. The problem was that, being relatively inexperienced in Javascript, I’ve never made my own ‘slideshow’ type application before. Usually I will either download an existing script (or [[shudder]] a FLASH object…) and modify it to fit my needs.

In my search to find the perfect rotator, I went through dozens, including the Teleirk Rad Rotator, obout Show, both of which, for the most part, worked pretty well and weren’t TOO difficult to customize…

Unfortunately, I didn’t like the approach that these (and as I discovered, most) controls used. Basically what happens is they load up a full array of images, create separate elements for each one, then toggle the visibility of each one in a cycle. This is pretty much the same problem I had that led me to build my Ajax News Rotator. Even worse, if javascript is disabled, the Rad Rotator renders an empty element! Other controls would experience erratic behavior as well, either rendering blank, or worse, showing ALL the elements in a mishmash of images. This problem was compounded by the fact that some of these controls (such as Rad Rotator) load all of the elements at once, meaning load time is stalled while all the content and images are loaded into their own separate frame!

And that’s when it dawned on me. All of these frames are exactly the same, so why do we need one for every single element? Ideally, we would only need the ONE frame, instead replacing it over and over with the content, loaded on demand with javascript. While this is certainly possible, having only one frame prevents us from doing transition effects, such as fade or slide, or anything that requires two elements to be blended together.

so my solution was to narrow it down to TWO frames, one for the start element, which is added statically on the page, and a second to “swap” the image, loaded dynamically at run time. Doing things this way allows the page to render correctly if Javascript is disabled, because it will just show the first frame. All I have to do now is swap the image back and forth across the two elements!

Neat huh? Alright, enough background, on to the code!

I’d like to start first by giving credit here to Rob Watson, because I used his excellent Fading Image Slide Show tutorial as a basis for my solution. His entry is pretty thorough, so I’m only going to touch on my changes.

The first thing to do was to remove the for-loop that appended every element, instead only adding a second img element to “swap” to. See instead of using the array to CREATE the img elements, I’m instead using the array to REPLACE the two elements with the SRC from the current item in the array!

// create new img element for rotationvar t = document.createElement('img');
t.setAttribute('src', images[nextImage]);
t.setAttribute('width', imageWidth);
t.setAttribute('height', imageHeight);
t.style.position = 'absolute';
t.style.visibility = 'hidden';
el.appendChild(t);

Next we want to grab a reference to these two elements so we can load them on demand and start the cycle.

// grab references to the two img elementsimgs = el.getElementsByTagName('img');

// begin rotationwindow.setTimeout(startFading, imageTimeout);

both startFading and setOpacity work pretty much the same as Rob’s:

function startFading()
{
    // grab the next element and fade it in var el = imgs[curImg];
    el.style.visibility = 'visible';
    el.style.zIndex = 2;
    setOpacity(el, 0);
    fadeImage(el, 0);

    // swap to the next img element curImg = (curImg == 1) ? 0 : 1;
}

function setOpacity(el, opacity)
{
    opacity /= 100;
    el.style.opacity = opacity;
    el.style.MozOpacity = opacity;
    el.style.filter = "alpha(opacity=" + (opacity * 100) + ")";
}

The biggest change comes in the fadeImage method. Basically the idea is to populate the NEXT image with the src from the images array and set it as hidden. Once that’s complete, all we have to do is fade it in:

function fadeImage(el, currentOpacity)
{
    // increase opacity currentOpacity += 2;

    // if full opaque if (currentOpacity > 100)
    {
        // reset in case over setOpacity(el, 100);

        // move to next image in array if (++nextImage > 4) nextImage = 0;

        // reset swapped img element to next image var prevEl = imgs[curImg];
        prevEl.setAttribute('src', images[nextImage]);
        prevEl.style.visibility = 'hidden';
        el.style.zIndex = 1;

        // continue rotation window.setTimeout(startFading, imageTimeout);
    }
    else {
        // fade in setOpacity(el, currentOpacity);
        window.setTimeout(function() { fadeImage(el, currentOpacity); }, 50);
    }

}

All that’s left to do now is call the function! Remember that we pre-populate the container div with the first image, that way if Javascript is disabled, users will still see the first (and only) image.

<asp:ScriptManagerProxy ID="ScriptManager" runat="server"> <Scripts> <asp:ScriptReference Path="/files/scripts/BannerSwapper.js" /> </Scripts></asp:ScriptManagerProxy><div id="fading_image_container"> <img src="/images/banners/banner10.jpg" alt="" style="width: 930px; height: 230px;
            position: absolute" /></div> <script type="text/javascript"> Init();
</script>

As you can see, I used a scriptmanagerproxy control to register my external js file, then call the Init function to get things started. Unfortunately this Init gets called on every page load, including partial postbacks (ajax) so if you’re using these, make sure to add a check in the javascript file to make sure that the second img element hasn’t already been added:

// make sure we're in the init phasvar el = document.getElementById('fading_image_container');
var count = el.getElementsByTagName('img').length;
if (count > 1) return;

And that’s all there is to it! You can see this in action at McAllen.net. The banner up at the top uses this script! But it won’t be up for long because it’s soon to get replaced with Part 2, where I wrap this script in a jQuery plugin! stay tuned!

Sitefinity: Rss Feeds for Generic Content – Part 3: Podcasts!

 To wrap up this series of posts, I’m going to briefly show you how to extend the Rss Feed Classes to enable support for podcasts. This is the exact same process I took to create my own Dark Synthesis Podcast, which is publishing entries from the the Generic Content Module.

The Generic Content module doesn’t have native support for uploading or associating files. This isn’t such a big deal for me anyway, however, since my files are hosted on Boxstr. So I figured the best way to accomplish this was to store a LINK to the enclosure file as a Meta Field, which I called SongUrl. This is done by adding the meta field to the web.config file:

<add key="Music.SongUrl" valueType="ShortText" visible="True" searchable="False" sortable="False" defaultValue=""/>

Now that this is in place, we basically we need to make changes in three places.

Rss Settings User Control

If you recall, this is the ascx User Control where we have the input fields for setting and updating settings for the Rss feed. We simply need to include an input field for the SongUrl.

<li> <label>Enclosure Meta Field</label> <asp:TextBox runat="server" ID="EnclosureMetaField" ValidationGroup="feedSettings" Columns="15" /></li> 

I decided to name this EnclosureMetaField, in case I (or anyone who might use this) chooses a different name for the Meta Field.

RssSettings Class

Since we added an input field to the Rss Settings User Control, we also need to make sure that we access this in our RssSettings class, specifically we need a reference to it in the ControlContainer class:

public IEditableTextControl EnclosureMetaField
{
    get {
        if (enclosureMetaField == null)
            enclosureMetaField = (IEditableTextControl)base.FindControl(typeof(IEditableTextControl), "EnclosureMetaField", true);

        return enclosureMetaField;
    }
}

We also need to make sure that we handle loading the data in the CreateChildControls method:

this.ctrlContainer.EnclosureMetaField.Text = this.settings["EnclosureMetaField"];

as well as saving the settings data in the SaveSettings method:

settings["EnclosureMetaField"] = this.ctrlContainer.EnclosureMetaField.Text;

Now we move on to the last piece of the puzzle.

RssChannelProvider

The last thing we need to do is use this meta field to create the enclosure. Luckily, the RssItem class already has support for enclosures, we just need to initialize it with the correct data. First we need to make sure that we retrieve this field from the settings:

private string EnclosureMetaField; public override void Initialize(IDictionary<string, string> settings)
{
    base.Initialize(settings);
    EnclosureMetaField = settings["EnclosureMetaField"];
    Category = settings["Category"];
    ItemCount = Int32.Parse(settings["ItemCount"]);
}

finally, we override the SetGeneralProps method and initialize and populate the enclosure property of the RssItem class. Note that we need to retrieve both the size and mime type of the item, which we do by calling the headers from the file using a WebRequest.

protected override void SetGeneralProps(RssItem item, IContent content)
    {
        if (content.GetMetaData(EnclosureMetaField) != null)
        {
            
            item.Enclosure = new RssEnclosure();
            item.Enclosure.Url = new Uri(content.GetMetaData(EnclosureMetaField).ToString());
            WebRequest req = WebRequest.Create(item.Enclosure.Url);
            req.Method = "HEAD";

            item.Enclosure.Length = Convert.ToInt32(req.GetResponse().ContentLength);
            item.Enclosure.Type = "audio/mpeg";
        }
        base.SetGeneralProps(item, content);
    }

And that is literally it! In my case, I know the files will always be mp3s, so I hard coded the mime type to avoid a second request (for some reason, I cannot retrieve both Length and Type in one call. I’m sure this can be fixed, I was just too lazy).

So there you have it! Custom rss feeds for the Generic Content module, including podcast/enclosure support! Now that you know how to do it, get out there and do it! and be sure to subscribe to my podcast. If you enjoy it, please consider donating your support to my shows host, Sanctuary Radio.

Thanks for reading, til next time!

Sitefinity: Rss Feeds for Generic Content – Part 2

 

As I mentioned in part one of this series, building an Rss feed for the Generic Content Module (or any module for that matter) requires three classes, RssChannelProvider, RssSettings, and RssView (plus the rss feed settings user control). We already covered the basic RssView, so now in this part we take a more detailed look at the changes required to the other two classes to implement the full Rss functionality.

RssSettings

The bulk of the rss setup is handled using this class, so most of our attention will be here. This is the class that facilitates the “setup” of your feed, including the feed title, description, and content to be syndicated. Since this is the link between the feed settings stored in the database and the settings user control, we use another class ControlContainer inside of the RssSettings class that handles references to all the input controls:

protected class ControlContainer : GenericContainer<GenericContentRssSettings>
{
    public ControlContainer(GenericContentRssSettings owner) : base(owner) { }

    private ListControl providerList;
    public ListControl ProviderList
    {
        get {
            if (providerList == null)
                providerList = base.FindRequiredControl<ListControl>("ProviderList");
            return providerList;
        }
    }

    private IEditableTextControl enclosureMetaField;
    public IEditableTextControl EnclosureMetaField
    {
        get {
            if (enclosureMetaField == null)
                enclosureMetaField = (IEditableTextControl)base.FindControl(typeof(IEditableTextControl), "EnclosureMetaField", true);

            return enclosureMetaField;
        }
    }

    private IEditableTextControl postUrl;
    public IEditableTextControl PostUrl
    {
        get {
            if (this.postUrl == null)
            {
                this.postUrl = (IEditableTextControl)base.FindControl(typeof(IEditableTextControl), "postUrl", true);
            }
            return this.postUrl;
        }
    }

    private IEditableTextControl summaryLength;
    public IEditableTextControl SummaryLength
    {
        get {
            if (this.summaryLength == null)
            {
                this.summaryLength = (IEditableTextControl)base.FindControl(typeof(IEditableTextControl), "summaryLength", true);
            }
            return this.summaryLength;
        }
    }

    private ListControl syndicationType;
    public ListControl SyndicationType
    {
        get {
            if (this.syndicationType == null)
            {
                this.syndicationType = base.FindRequiredControl<ListControl>("syndicationType");
            }
            return this.syndicationType;
        }
    }

    private IEditableTextControl itemsCount;
    public IEditableTextControl ItemsCount
    {
        get {
            if (this.itemsCount == null)
            {
                this.itemsCount = (IEditableTextControl)base.FindControl(typeof(IEditableTextControl), "itemsCount", true);
            }
            return this.itemsCount;
        }
    }

    private ListControl categoryList;
    public ListControl CategoryList
    {

        get {
            if (this.categoryList == null)
            {
                this.categoryList = base.FindRequiredControl<ListControl>("CategoryList");
            }
            return this.categoryList;
        }
    }
}

Note that this control uses the generic ListControl and IEditableTextControl instead of DropDownList and TextBox. This allows you to use more complex versions of these controls (such as Telerik RadControls) without breaking functionality. All this class does is return references to the individual input controls on the Rss Feed Settings User Control I mentioned last time. You might want to include error handling (such as if the control is not found, or if the control is of the wrong type).

Now that we have the link between settings and user input, we need to override the CreateChildControls method. This is the method that instantiates all the input user controls using the ControlContainer class and populates them with existing settings (if any) so that they can be saved to the database on creating (or update). I’ve done my best to comment the sequence of events below.

 

protected override void CreateChildControls()
{
    ctrlContainer = new ControlContainer(this);

    // instatniate the settings user control if (this.controlTemplate == null)
    {
        // make sure it exists if (File.Exists(this.Page.MapPath(this.ControlTemplatePath)))
        {
            this.controlTemplate = this.Page.LoadTemplate(this.ControlTemplatePath);
        }
        else {
            throw new NullReferenceException("Control Template does not exist");
        }
    }
    this.controlTemplate.InstantiateIn(this.ctrlContainer);

    // retrieve list of content providers string[] strArray = null;
    int count = ConfigHelper.Handler.Providers.Count;
    if (count > 1)
    {
        int num2 = 0;
        strArray = new string[count];
        foreach (ProviderSettings element in ConfigHelper.Handler.Providers)
        {
            // we only want the generic content providers if (element.Type.StartsWith("Telerik.Cms.Engine.Data.Providers.DefaultProvider"))
            {
                // make sure we have permission GlobalPermission permission = new GlobalPermission((GlobalPermissions)ContentManager.SecurityRoots[element.Name], CrudRights.View);
                if (permission.CheckDemand())
                {
                    // add to list of providers strArray[num2++] = element.Name;
                }
            }
        }
    }

    // add list of providers to dropdown list for selection ListControl providerList = this.ctrlContainer.ProviderList;
    if (strArray != null)
    {
        foreach (string str in strArray)
        {
            if (!string.IsNullOrEmpty(str))
            {
                ListItem item = new ListItem(str.Replace('_', ' '), str);
                providerList.Items.Add(item);
            }
        }
    }
    this.ctrlContainer.ProviderList.Visible = providerList.Items.Count > 1;
    this.ctrlContainer.ProviderList.AutoPostBack = true;

    // handle changes to selected provider to show its categories this.ctrlContainer.ProviderList.SelectedIndexChanged += new EventHandler(this.ProviderList_SelectedIndexChanged);

    // populate syndication type menu this.ctrlContainer.SyndicationType.Items.Add(new ListItem("Full Content", Enum.GetValues(typeof(SyndicationType)).GetValue(0).ToString()));
    this.ctrlContainer.SyndicationType.Items.Add(new ListItem("Summary", Enum.GetValues(typeof(SyndicationType)).GetValue(1).ToString()));
    this.ctrlContainer.SyndicationType.Items.Add(new ListItem("Title Only", Enum.GetValues(typeof(SyndicationType)).GetValue(2).ToString()));

    // grab settings from db and load into user control if ((this.settings != null) && (this.settings.Count > 5))
    {
        this.ctrlContainer.SyndicationType.SelectedValue = this.settings["SyndicationType"];
        this.ctrlContainer.ProviderList.SelectedValue = this.settings["ProviderName"];
        this.ctrlContainer.PostUrl.Text = this.settings["ItemUrl"];
        this.ctrlContainer.ItemsCount.Text = this.settings["ItemCount"];
        this.ctrlContainer.SummaryLength.Text = this.settings["SummaryLength"];
    }

    // set Provider and category settings["ProviderName"] = ctrlContainer.ProviderList.SelectedValue;
    settings["Category"] = ctrlContainer.CategoryList.SelectedValue;

    // load category menu and select current option PopulateCategories();
    if (this.ctrlContainer.CategoryList.Items.FindByValue(settings["Category"]) != null)
        this.ctrlContainer.CategoryList.SelectedValue = settings["Category"];

    // render control to the page this.Controls.Add(this.ctrlContainer);
}

You may have noticed that I added an event handler to the SelectedIndexChanged event in the ProviderList menu. This calls a method to retrieve the list of categories for that provider so that we can show its categories when we change to it. This method calls the same PopulateCategories() method from the initial setup.

protected void ProviderList_SelectedIndexChanged(object sender, EventArgs e)
{
    ListControl lst = sender as ListControl;
    settings["ProviderName"] = lst.SelectedValue;
    PopulateCategories();
}

private void PopulateCategories()
{
    this.ctrlContainer.CategoryList.Items.Clear();
    string provider = this.settings["ProviderName"];
    ContentManager mgr = new ContentManager(provider);
    IList cats = mgr.GetCategories();
    foreach (Telerik.Cms.Engine.Data.Category cat in cats)
    {
        this.ctrlContainer.CategoryList.Items.Add(cat.CategoryName);
    }
}

This allows us to syndicate only specified categories of content from the specific provider. I did this because I only want to publish a feed of my Dark Synthesis Mixes in my podcast, as opposed to all of my music. You will see how this works in the next section, however this can easily be modified to ignore the category meta field and show all content from the provider.

The last thing we need to do for this class is make sure that we have a method to save the settings to the database. This is done by overriding the SaveSettings() method.

public IDictionary<string, string> SaveSettings()
{
    settings = new Dictionary<string, string>();
    settings["ProviderName"] = this.ctrlContainer.ProviderList.SelectedValue;
    settings["ItemUrl"] = this.ctrlContainer.PostUrl.Text;
    settings["ItemCount"] = this.ctrlContainer.ItemsCount.Text;
    settings["Category"] = this.ctrlContainer.CategoryList.SelectedValue;
    settings["SyndicationType"] = this.ctrlContainer.SyndicationType.SelectedValue;
    settings["SummaryLength"] = this.ctrlContainer.SummaryLength.Text;
    return settings;
}

That’s pretty much it for this class. As you can see, editing settings for the feed is pretty straight forward. Just make sure you add the control to the Feed Settings User Control, as well as a proeprty to the ControlContainer class to reference it. Then make sure that your settings are saved to the database in the SaveSettings and loaded in the CreateChildControls method. Now lets move on to the class that actually creates the feed itself!

RssChannelProvider

This is the class that is responsible for building the feed itself. Luckily, the mechanics of actually writing the xml are abstracted away by an RssItem class, so all we have to do is create these items and pass them along to the class and it will handle the rest! The first thing you want to do is expand the Initialize method to extract the relevant settings from the internal settings property (this is the same dictionary of elements from the previous RssSettings class) and map them to local private variables for later use.

private string EnclosureMetaField;
private string Category;
private int ItemCount;
public override void Initialize(IDictionary<string, string> settings)
{
    base.Initialize(settings);
    Category = settings["Category"];
    ItemCount = Int32.Parse(settings["ItemCount"]);
}

Next we need to override the CreateDataSource method to retrieve the appropriate list of items using the settings  values we just retrieved.

protected override IList CreateDataSource()
{
    ContentManager mgr = new ContentManager(this.ProviderName);
    if (string.IsNullOrEmpty(Category))
        return mgr.GetContent("Publication_Date DESC");
    else {
        List<IMetaSearchInfo> filter = new List<IMetaSearchInfo>();
        filter.Add(new MetaSearchInfo(MetaValueTypes.ShortText, "Category", Category));
        return mgr.GetContent(0, ItemCount, "Publication_Date DESC", filter.ToArray());
    }
}

As you can see, this is just a matter of initializing the content manager with the correct ProviderName. the base Initialize already populates the internal ProviderName property with the correct provider name! Notice how we make use of the Category property to filter the results and only show the requested category. It is here that you can make changes to the list of items that are retrieved based on whatever logic you might require to suit the needs of your feed.

Now the DataSource creates an IList of IContent, which is the basic GenericContent class. We need to map this into the RssItem class so that it can be properly rendered. So the final method we need to override is GetRssItems, which creates the individual items for the feed.

public override IList<RssItem> GetRssItems(System.Collections.Specialized.NameValueCollection query)
{
    IList<RssItem> rssItems = base.GetRssItems(query);
    if (rssItems.Count == 0)
    {
        RssItem item = new RssItem();
        item.Title = "Empty Blog";
        rssItems.Add(item);
    }
    return rssItems;
}

As you can probably tell, most of the work is handled by the base method. However, if the feed contains no items, you are going to get an exception stating that the feed cannot be closed without writing an entry. Basically what this means is that you have an empty blog. I handle this exception by checking for an empty list and if that is the case, creating a blank dummy item to show information and prevent the error. You could also use this method to do any last minute changes to your feed items (such as changing the publish time, title, or anything else you might need to modify at the last moment before publication).

And that’s pretty much it! With just these two parts you have the basics for creating your own feeds from GenericContent, or any custom module in Sitefinity, once again proving the versatility and extensibility of this great CMS package (not to mention how much FUN it is!). We have just one more part to go, which is modifying the feed to handle enclosures (read: podcasts!). Stay tuned!

Sitefinity: Rss Feeds for Generic Content – Part 1: Setting Up

After finally getting my music back online earlier this week, the next logical step was to get my Dark Synthesis Podcast back online. Unfortunately, Sitefinity (the CMS this site uses) does not have built in support for podcasts. In fact, the Generic Content Module (which I am using to publish my music, and eventually hopefully my videos) doesn’t even have support for general rss feeds! Fortunately to me, however, this wasn’t a setback; it was yet another opportunity to dive deeper into Sitefinity and learn to do something new!

In a recent forum on the Sitefinity forum, I discussed how to implement rss feeds for Sitefinity intra-site modules. This actually works great, and if any one is interested I’d be glad to make a more detailed blog entry about how this is done… But this site doesn’t use custom modules (not yet anyway); music and videos are published using the Generic Content module. The only Rss providers built into Sitefinity are for Pages, Blogs, and Forums.

Fortunately, in what is turning out to be a life-changing moment, I discovered Reflector, possibly one of the greatest tools a .NET developer can have! With it, I was able to take a peek at the existing rss providers (I used the Blog Rss Provider as an example) and use them as a guide to build my very own implementation for the Generic Content Module!

As a result I’m proud to begin the first of a three-part series on how to create an Rss Feed from the Generic Content Module. Part 1 introduces the various classes and controls that need to be created. Part 2 will show you the various methods that need to be overridden within each class. Finally Part 3 will show you how to turn your Rss Feed into a Podcast!

Okay enough back story… let’s get to the code!

Required Classes

There are three classes that will need to be created: a ChannelProvider, RssSettings, and RssView. I’ll introduce each class briefly here, then go into more detail of each one later on in this post.

The real meat of the Rss feed comes from the ChannelProvider and RssSettings classes; rhe RssView class is only used to show a read-only view of the Rss settings in the Services page, so a full implementation isn’t required. Since I didn’t really use this, but since it is required, I’ll just display the minimum code which I used to get it functioning:

using System;
using System.Collections.Generic;
using System.Web;

public class GenericContentRssViewControl : System.Web.UI.WebControls.CompositeControl, Telerik.Framework.Rss.IRssViewControl{
    #region IRssViewControl Members

    IDictionary<string, string> _settings;
    public void InitializeSettings(IDictionary<string, string> settings)
    {
        _settings = settings;
    }  

    #endregion}

Now you need to create a class that inherits from the RssChannelProvider. The basic overrides are below, but we’ll be jumping back and forth as we create the additional classes.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Net;
using Telerik.Rss;
using Telerik.Cms.Engine;

public class GenericContentRssProvider : RssChannelProvider{
    public override void Initialize(IDictionary<string, string> settings)
    {
        base.Initialize(settings);
    }

    public override string Name
    {
        get { return "Generic Content"; }
    }

    public override string Description
    {
        get { return "Rss Provider for the Generic Content Module"; }
    }
}

You are also going to need to create a RssSettings class, but instead of inheriting, we’ll be implementing the IRssSetings interface. This class also needs to contain within it a ControlContainer class. This container class is the link between this settings class and the actual ascx user control that renders on the admin webpage.

using System;
using System.IO;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Telerik.Cms.Engine;
using Telerik.Cms.Engine.Configuration;
using Telerik.Cms.Engine.Security;
using Telerik.Cms.Web.UI;
using Telerik.Security.Permissions;
using Telerik.Framework.Rss;
using Telerik.Rss;

public class GenericContentRssSettings : CompositeControl, IRssSettingsControl{
    private PropertyDescriptorCollection properties;
    private PropertyDescriptorCollection Properties
    {
        get {
            if (this.properties == null)
            {
                this.properties = TypeDescriptor.GetProperties(this);
            }
            return this.properties;
        }
    }

    private ITemplate controlTemplate;
    private ControlContainer ctrlContainer;

    public string ControlTemplatePath
    {
        get {
            object obj2 = this.ViewState["ControlTemplatePath"];
            if (obj2 == null)
            {
                return "~/Sitefinity/Admin/ControlTemplates/Generic_Content/RssSettingsControlTemplate.ascx";
            }
            return (string)obj2;
        }
        set {
            this.ViewState["ControlTemplatePath"] = value;
        }
    }

    private IDictionary<string, string> settings;
    public void InitSettings(IDictionary<string, string> settings)
    {
        this.settings = settings;
    }

    #region IRssSettingsControl Members

    public IDictionary<string, string> SaveSettings()
    {
        settings = new Dictionary<string, string>();
        return settings;
    }

    #endregion protected class ControlContainer : GenericContainer<GenericContentRssSettings>
    {
        public ControlContainer(GenericContentRssSettings owner) : base(owner) { }

    }
}

In addition to these basic classes, we also need the front-end editor web control for editing feed settings in Sitefinity. This is basically a User Control that contains the various input fields for the Rss Feed settings we want to be able to modify, including Title, Description, number of items, etc. This is also the User Control that is referenced in the ControlTemplatePath property shown above.

I used the Blogs Rss Settings Control (from the /sitefinity/admin/controltemplates/blog folder) as an example, and here is what I came up with. Notice how I’ve included a dropdownlist control so that you can select the data from the specific available content providers.

<div class="RssGenericContentChannels"> <ul> <li runat="server" id="providerNameLi"> <label>Content Provider</label> <asp:DropDownList runat="server" ID="ProviderList"></asp:DropDownList> </li> <li class="selector clearfix"> <asp:Label ID="Label8" runat="server" AssociatedControlID="postUrl"> <asp:Literal ID="Literal1" runat="server" Text="Post Url" ></asp:Literal> <cc1:LabelToolTip HelpBoxCssClass="HelpBox" id="labelHelpBox3" runat="server" ToolTipTitle="" ToolTipText="" AlternateText=""></cc1:LabelToolTip> </asp:Label> <asp:TextBox runat="server" ID="postUrl"></asp:TextBox> <asp:LinkButton runat="server" ID="selectPostUrl" Text="Select Url" CssClass="picker" ></asp:LinkButton> </li> <li class="count clearfix"> <asp:Label ID="Label3" runat="server" Text="Item Count" AssociatedControlID="itemsCount"></asp:Label> <asp:TextBox runat="server" ID="itemsCount" ValidationGroup="feedSettings" Text="15"></asp:TextBox> <em class="quont">items</em> </li> <li class="clearfix"> <asp:Label ID="Label6" runat="server" Text="Syndication Type" AssociatedControlID="syndicationType"></asp:Label> <asp:DropDownList runat="server" ID="syndicationType"></asp:DropDownList> </li> <li runat="Server" id="summaryLengthLi" class="count clearfix"> <asp:Label ID="Label5" runat="server" Text="Summary Length" AssociatedControlID="summaryLength"></asp:Label> <asp:TextBox runat="server" ID="summaryLength" ValidationGroup="feedSettings" Text="500"></asp:TextBox> <em class="quont">symbols</em> </li> <li> <label>Category</label> <asp:DropDownList ID="CategoryList" runat="server" /> </li> </ul></div>

So that’s the basic setup, you need to define the three classes as well as the User Control that contains the input fields for editing the feed settings. In my next post, I’ll discuss and show you the various methods in these classes that need to be overridden to provide access to editing and saving these settings, and finally to create the feed itself.

Stay tuned!

Technorati Tags: ,,,,