Sunday, April 3, 2011

ASP.NET Slideshow Control with jQuery and XML

Introduction

For the past few years, image sliders, slide-shows, and ad rotator scripts have become increasingly popular for web pages. There are thousands of these scripts on the Web, but it was difficult to find one that fit my requirements. I wanted a script that is flexible enough for me to use as an ad rotator, slide-show, or image rotator with navigation controls and that extracts the slides information from an XML file. After spending some time researching for it, I found the jQuery based slideshow with navigation controls from Dynamic Drive and the article Reading XML with jQuery. I had put together these findings and a brief tutorial "jQuery slideshow with XML". Recently, I decided to encapsulate this into ASP.NET User Controls to increase the reusability of code. Now, I can have multiple instances of this control on my page, and I can customize the width and height, options to hide or show navigation controls, XML file source, and other settings for each instance. I have put together a step by step tutorial on how I have accomplished this.
Figure 1
Sample results

Check list

Getting started

Before we begin, make sure to download a copy of the Simple Controls Gallery v1.3 from Dynamic Drive, Reading XML with JQuery, and the latest version of the jQuery JavaScript library. Here is the structure of my project. You are welcome to download this demo.
Figure 2
Project Structure

Putting everything together

SlideShow.ascx.cs

First, add a Web User Control to the project and create public properties and methods to allow interaction with the control at design-time or programmatically.
Listing 1

//slide divpublic string WrapperID
{
    get { return this.ClientID + "Div"; }
}
WrapperID will return the unique ID of the User Control. Every time we drop an ASP.NET User Control on to a page, it will automatically include an ID property that uniquely identifies the control. The ID value is the combination of the tag name and a number. If the tag name of the User Control is "SlideShow", WrapperID will return SlideShow1Div, SlideShow2Div …SlideShowNDiv.
Listing 2
Collapse
private int _width =728;
[DefaultValue(728)]
public int Width
{
    set { this._width = value; } get { return this._width; }
}
//height of the slideprivate int _height = 95;
[DefaultValue(95)]
public int Height
{
    set { this._height = value; }  get { return this._height; }
}
Add the Width and Height properties to allow us to adjust the height and width of the div control.
Listing 3

// autoplay true|false private bool _autoPlay = true;
[DefaultValue(true)]
public bool AutoPlay
{
    get { return this._autoPlay; }  set { this._autoPlay = value; }
}

// Show Navigation Control true|falseprivate bool _showNavigation = true;
[DefaultValue(true)]
public bool ShowNavigation
{
    get { return this._showNavigation; } set{ this._showNavigation = value; }
}

private int _delay_btw_slide = 10000;
/// delay between slide in miliseconds [DefaultValue(10000)]
public int Delay_btw_slide
{
    set { this._delay_btw_slide = value; }   get { return this._delay_btw_slide; }
}

private int _fadeDuration = 2000;
/// transition duration (milliseconds) [DefaultValue(2000)]
public int FadeDuration
{
    set { this._fadeDuration = value; }  get { return this._fadeDuration; }
}
private int _cycles_before_stopping = 99;
/// cycles befote stopping[DefaultValue(99)]
public int Cycles_before_stopping
{
    set { this._cycles_before_stopping = value; } 
    get { return this._cycles_before_stopping; }
}

// previous buttonprivate string _btnPrevious = "~/images/previous.gif";
[Category("Appearance"), Localizable(true)]
[Description("Previous button"), 
 DefaultValue("~/images/previous.gif"), Bindable(true)]
[Editor("System.Web.UI.Design.ImageUrlEditor, 
         System.Design, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]    [UrlProperty]
public string BtnPrevious
{
    get { return this._btnPrevious; } set { this._btnPrevious = value;}
}

// Next button// Play button // Pause button
  • AutoPlay - true = start the slide show automatically without pressing the Play button and vice versa.
  • ShowNavigation - true = show the navigation control, or false = hide the navigation control.
  • Delay_btw_slide - get or set the delay between each image slide.
  • FadeDuration - get or set the fade duration between each slide.
  • Cycles_before_stopping - get or set the number of rotations before stopping the rotation. If we set 99, it will rotate 99 times and stop until we hit the Play button again.
  • BtnPrevious, BtnNext, BtnPlay, BtnPause - get or set the image for the Previous, Next, Play, and Pause buttons, respectively. These properties accept relative URLs or absolute URL paths to the button images. Make sure to place the images folder at the root directory of the application if you are using relative URLs.
Listing 4

//xml fileprivate string _xmlSource = "~/xml/sites.xml";
[UrlProperty]
[Bindable(true)]
[DefaultValue("~/xml/sites.xml")]
public string XMLSource
{
    get { return this._xmlSource; } set { this._xmlSource = value; }
}

//xPathprivate string _xPath = "site";
[DefaultValue("site")]
public string xPath
{
    get { return this._xPath; }  set { this._xPath = value; }
}
Add a property to get and set the XML source path. The default path is set to ~/xml/sites.xml. This property accepts a relative URL or absolute URL path to the XML file. Make sure to place the XML folder at the root directory of the application if you are using relative URL. Then, add another property, XPATH, to navigate through elements and attributes in the XML document.
Listing 5

void CreateScript()
{
    StringBuilder ssScript = new StringBuilder(string.Empty);
    string arrName = "myArray" + this.WrapperID;

    //read XML    ssScript.Append("var " + arrName+ "= [];");
    ssScript.Append("$(document).ready(function() {");
    ssScript.Append(" $.ajax({");
    ssScript.Append("type: \"GET\",");
    ssScript.Append("url: '" + ResolveUrl(XMLSource) + "',");
    ssScript.Append("cache: true,");
    ssScript.Append("dataType: \"xml\",");
    ssScript.Append("success: function(xml) {");
    ssScript.Append("var count = 0;");
    ssScript.Append("$(xml).find('" + xPath + "').each(function() {");

    ssScript.Append(" var url = $(this).find('url').text();");
    ssScript.Append("var target = $(this).find('target').text();");
    ssScript.Append("var imageURL = $(this).find('imageURL').text();");
    ssScript.Append("var alt = $(this).find('alt').text();");

    ssScript.Append(arrName + "[parseInt(count)] = 
             new Array(imageURL, url, target, alt); ");
    ssScript.Append("count++;");
    ssScript.Append("});");

    //slide-shows    ssScript.Append(" var mygallery"+this.WrapperID+" = new simpleGallery({");
    ssScript.Append(" wrapperid: '" + this.ClientID + 
                    "_" + this.WrapperID + "',");
    //width/height of gallery in pixels.    //Should reflect dimensions of the images exactly    ssScript.Append("dimensions: [" + Width.ToString() + 
                    ","+ Height.ToString()+"],"); 
    ssScript.Append("imagearray: "+arrName+","); //array of images    ssScript.Append("navimages: ['" + ResolveUrl(BtnPrevious) + "', '" + 
                    ResolveUrl(BtnPlay) + "', '" + ResolveUrl(BtnNext) + 
                    "', '" + ResolveUrl(BtnPause) + "'],");
    ssScript.Append("showpanel: '" + ShowNavigation.ToString().ToLower() + "',");
    ssScript.Append(" autoplay: [" + AutoPlay.ToString().ToLower() + "," + 
                    Delay_btw_slide.ToString() + "," + 
                    Cycles_before_stopping.ToString() + "],"); 
    //[auto_play_boolean, delay_btw_slide_millisec, cycles_before_stopping_int]    ssScript.Append(" persist: true,");
    //transition duration (milliseconds)    ssScript.Append(" fadeduration:" + FadeDuration.ToString() + ",");
    //event that fires when gallery has initialized/ ready to run    ssScript.Append(" oninit: function() {");
    ssScript.Append("  },");
    //event that fires after each slide is shown    ssScript.Append("  onslide: function(curslide, i) {");
    //curslide: returns DOM reference to current    // slide's DIV (ie: try alert(curslide.innerHTML)    //i: integer reflecting current image within collection    //   being shown (0=1st image, 1=2nd etc)    ssScript.Append("   }");
    ssScript.Append("  })");
    ssScript.Append("  }");
    ssScript.Append("   });");
    ssScript.Append(" });");

        ClientScriptManager jScript = Page.ClientScript;
        jScript.RegisterClientScriptBlock(this.GetType(), 
                Guid.NewGuid().ToString(), ssScript.ToString(), true);
    }
Then, add a method to render the JavaScript programmatically. The main purpose of this JavaScript is to read the slide information from the XML file into an array and initialize the slide-show properties. The key items of this JavaScript are the array variable, HTML div control, and the slide-shows variable. In order to have multiple instances of slide-shows on the page, we have to ensure that the unique IDs and name are being assigned to the mentioned key items. We can utilize the WrapperID property to create unique names and IDs for each instance. Note that the ResolveUrl method is being utilized so that the browser can resolve the URL of the images and the XML file. The rest of the code is very straightforward; you can download the original code from Dynamic Drive to compare the changes.
Listing 6

void CreateDiv()
{
    System.Web.UI.HtmlControls.HtmlGenericControl ssDivWrapper = 
       new System.Web.UI.HtmlControls.HtmlGenericControl("div");
    ssDivWrapper.ID = this.WrapperID;
    ssDivWrapper.Style.Add("background", "white none repeat scroll 0% 0%");
    ssDivWrapper.Style.Add(HtmlTextWriterStyle.Overflow, "hidden");
    ssDivWrapper.Style.Add(HtmlTextWriterStyle.Position, "relative");
    ssDivWrapper.Style.Add(HtmlTextWriterStyle.Visibility, "visible");
    ssDivWrapper.Style.Add(" -moz-background-clip", "border");
    ssDivWrapper.Style.Add("-moz-background-origin", "padding");
    ssDivWrapper.Style.Add("-moz-background-inline-policy", "continuous");

    this.Controls.Add(ssDivWrapper);
}
The CreateDiv method will generate the HTML div on the page dynamically.
Listing 7

//load the javascriptinternal void LoadJScript()
{
    ClientScriptManager script = Page.ClientScript;
    //prevent duplicate script    if (!script.IsStartupScriptRegistered(this.GetType(), "JQuerySlideShowJS"))
    {
        script.RegisterClientScriptBlock(this.GetType(), "JQuerySlideShowJS",
           "");
    }

    if (!script.IsStartupScriptRegistered(this.GetType(), "SimpleGalleryJS"))
    {
        script.RegisterClientScriptBlock(this.GetType(), "SimpleGalleryJS",
           "");
    }
}
The purpose of the LoadJScript method is to load jquery-1.3.2.min.js and simplegallery.js dynamically with no duplicates. With that being said, if we drag 10 slide-show controls on to a page, it will only add the JavaScript once on to the ASP.NET page. This will help avoid unnecessarily assembling the client-side script.

simplegallery.js

I'm not going to post the whole contents of this JavaScript here, but I'll list the changes that I have made.
  1. Removed the static image button properties from the simpleGallery_navpanel interface since the image buttons will be assigned dynamically through the CreateScript method.
  2. Modified the script to read the image button properties from different interfaces.
  3. Added logic to hide and display the navigation control through the ShowNavigation property.
  4. Added alternate text to the image buttons.

Using the Code

Listing 8

<uc1:SlideShow ID="SlideShow2" runat="server" />
Drag and drop the User Control on to the page. Here is the default setting of the User control:
Figure 3
Default properties
Listing 9

<uc1:SlideShow ID="SlideShow5" runat="server" 
    BtnNext="~/images/nav-arrow-right.gif" 
    BtnPrevious="~/images/nav-arrow-left.gif" 
    AutoPlay="false" />
Above is an example on how to set the image buttons and the AutoPlay property.
Listing 10

<uc1:SlideShow ID="SlideShow7" runat="server" XPath="site500x281"  
    Width="500" Height="281" ShowNavigation="true" 
    XMLSource="~/xml/500x281.xml"/>
The above example demonstrates how to set the width and height of the HTML div control. The control gets the images information from the 500x821.xml file and selects the site500x281 nodes. By default, the ShowNavigation attribute is set to true.
Listing 11 - More sample usage
 
<uc1:SlideShow ID="SlideShow6" runat="server" BtnNext="~/images/nav-arrow-right.gif" 
        BtnPrevious="~/images/nav-arrow-left.gif" 
        XMLSource="~/xml/120x600.xml" XPath="site120x600" 
         Width="120" Height="600" />

<uc1:SlideShow ID="SlideShow1" XPath="site120x60" 
    Width="120" Height="60"
    ShowNavigation="false" 
    runat="server" XMLSource="~/xml/sites2.xml"  />

Conclusion

If you are not sure of the relative path to the images or XML file, I would suggest using absolute links. Please keep in mind that the links to the XML files have to be in the same domain, no cross-domain calls. Also, try not to place all the images information in one XML file for multiple slide-show controls. Imagine, if we have a XML file of 50KB in size, 10 slide-show controls on the page, and each of them consuming the same XML source. It will cost us 500KB bandwidth and degrade the loading speed. I hope someone will find this tutorial useful and share some thoughts with me on how to make it better.
Tested on IE 6.0/7.0/8.0, Google Chrome, and Firefox.

References

No comments:

Popular Posts