Click here to Skip to main content
Licence Apache
First Posted 24 Nov 2011
Views 52,453
Downloads 9,022
Bookmarked 135 times

ASP .NET Image Slider with fancy thumbnail hover effect

By | 2 Apr 2012 | Article
This article aims to show that ASP .NET combined with jQuery/Ajax/Javascript produces user friendly and attractive websites.

Introduction   

This article aims to show that ASP .NET combined with jQuery/Ajax/Javascript produces user friendly and attractive websites. 

The application is an ASP .NET Website that provides images visualization with a fancy thumbnail hover effect.  If you want to use the image slider view in your ASP .NET application, you can encapsulate it in a ASP .NET control, and then put it in a DLL. 

In this article, I describe the first version of the application and its updates. In the description of each version, there are sections that contains mock-ups, the description of the server side code, and the description of the client side code.

In order to focus on the fancy thumbnail hover effect, I decided to make the architecture of the application as simple as possible. I have used XML to persist data, the Repeater server control with paging, jQuery, Ajax, and CSS.  

A video demonstration of the update 2 is available on YouTube.  

Browser compatibilty  

All the versions available have been tested on IE7, IE8, IE9, Firefox, Chrome, Safari and Opera, and are browser compatible with IE7, IE8, IE9, Firefox, Chrome, Safari and Opera.

The directive <!DOCTYPE html> at the top of the Default.aspx page is very important to ensure that the page will be rendered properly.

Models  

jQueryImageSlider/jQuery.ImageSlider1.PNG

jQueryImageSlider/jQuery.ImageSlider2.PNG

jQueryImageSlider/jQuery.ImageSlider4.PNG

jQueryImageSlider/jQuery.ImageSlider3.PNG

Using the code

The XML structure is defined as bellow.

<images>
  <image imageUri="/Images/DSC_0349_k.JPG" thumbnailUri="/Images/DSC_0349_k_thumb.JPG" />
  <image imageUri="/Images/DSC_0359_nb.JPG" thumbnailUri="/Images/DSC_0359_nb_thumb.JPG" />
</images>

Every image has a preview file whose location is set in the imageUri attribute and a thumbnail file whose location is set in the thumbnailUri attribute. You will notice that all the images are stored in the folder Images in the root of the Web site.

I decided to use the Repeater server control because it responds to my need. This control is a data-bound list that allows custom layout by repeating a specified template for each item displayed in the list.

<asp:Repeater ID="RepeaterImages" runat="server" EnableViewState="false">
    <HeaderTemplate>
        <ul class="thumb">
    </HeaderTemplate>
    <ItemTemplate>
        <li><a href='<%#DataBinder.Eval(Container.DataItem, "imageUri")%>'>
            <img src='<%#DataBinder.Eval(Container.DataItem, "thumbnailUri")%>' alt="" />
        </a></li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>

In the layout, I used an unordered list ul. Each item of the list contains the thumbnail and a link to its preview.

The binding is done by the method LoadImages.

private void LoadImages()
{
    // Populate the repeater control with the images DataSet
    // Indicate that the data should be paged
    // Set the number of images you wish to display per page
    // Set the PagedDataSource's current page
    var pds = new PagedDataSource
                    {
                        DataSource = ImagesDataView,
                        AllowPaging = true,
                        PageSize = 9,
                        CurrentPageIndex = CurrentPage - 1
                    };

    LabelCurrentPage.Text = "Page " + CurrentPage + " of " + pds.PageCount;

    // Disable Previous or Next buttons if necessary
    ImageButtonPrevious.Visible = !pds.IsFirstPage;
    ImageButtonNext.Visible = !pds.IsLastPage;

    // DataBind
    RepeaterImages.DataSource = pds;
    RepeaterImages.DataBind();
}

The current page number is persisted in the ViewState.

public int CurrentPage
{
    get
    {
        // Look for current page in ViewState
        object o = ViewState["CurrentPage"];
        if (o == null) return 1; // default page index of 1
        return (int) o;
    }

    set { ViewState["CurrentPage"] = value; }
}

The DataView is persisted in the Cache to boost paging performance.

public DataView ImagesDataView
{
    get
    {
        if (Cache["ImagesDataView"] == null)
        {
            // Read images info from XML document into a DataSet
            var d = new DataSet();
            d.ReadXml(MapPath("/App_Data/Images.xml"));
            Cache["ImagesDataView"] = d.Tables[0].DefaultView;
        }
        return (DataView)Cache["ImagesDataView"];
    }
}

The paging is done through two buttons and a label that contains the current page number.

<div id="pager">
    <asp:Label ID="LabelCurrentPage" runat="server" EnableViewState="false"></asp:Label>
    <asp:ImageButton ID="ImageButtonPrevious" runat="server" ImageUrl="Styles/Images/Arrow-Left-icon.png"
        OnClick="ImageButtonPrevious_Click" CssClass="previous" EnableViewState="false" />
    <asp:ImageButton ID="ImageButtonNext" runat="server" ImageUrl="Styles/Images/Arrow-right-icon.png"
        OnClick="ImageButtonNext_Click" CssClass="next" EnableViewState="false" />
</div>

If the page is being rendered for the first time or isn't being loaded in response to a PostBack, we load the images.

protected void Page_Load(object sender, EventArgs e)
{
    // Reload control if the page is being rendered for the first time
    // or isn't being loaded in response to a postback
    if (!IsPostBack)
    {
        LoadImages();
    }
}

When the user clicks on the next button, the current page number is updated and the images are loaded.

protected void ImageButtonNext_Click(object sender, EventArgs e)
{
    // Set viewstate variable to the next page
    CurrentPage++;

    // Reload control
    LoadImages();
}

When the user clicks on the previous button, the current page number is updated and the images are loaded.

protected void ImageButtonPrevious_Click(object sender, EventArgs e)
{
    // Set viewstate variable to the previous page
    CurrentPage--;

    // Reload control
    LoadImages();
}

The icon buttons that I used are free and from Icon Archive.

The images that I used are free and from morgueFile.

You will notice that I disabled the SessionState because I don't need it and for performance reasons If you don't need it is recommended to disable it.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="jQuery.ImageSlider.Default" EnableSessionState="false" %> 

Description of the Update 1

I had a feedback on my blog and on codeproject to display image metadata when the end-user clicks on the thumbnail. I decided to update the article to take this feedback in consideration.

In the version without metadata, Loading the images from the XML into a DataSet was sufficient. However, now that we need to display the metadata in the UI we have to retrieve it from the image file and encapsulate all the image informations into an object then update the binding of the images Repeater.

Models

jQueryImageSlider/sc0.jpg

jQueryImageSlider/sc2.PNG

jQueryImageSlider/sc3.PNG

jQueryImageSlider/sc5.jpg

Loading the image metadata

I used MediaInfo.dll to load the metadata from the image file. MediaInfo supplies technical and can read tag/atom information about a video, audio or image file containers without loading the entire file in memory.

MediaInfo is developed in C++, and is cross-platform (Win32, Linux, and Mac), open source code (GPL/LGPL dual-license), and can be compiled into a .dll (Dynamic link library) on Windows, .so (Shared Object) on Linux, .dynlib (Dynamic Library) on Mac. 

The MediaInfo dll have to be placed in the application executable folder.

I created a new class named Metadata to encapsulate the metadata Key and Value.

namespace jQuery.ImageSlider
{
    public class Metadata
    {

        #region Constructor

        public Metadata(string key, string value)
        {
            Key = key;
            Value = value;
        }

        #endregion

        #region Properties

        public string Key { get; private set; }
        public string Value { get; set; }

        #endregion

    }
}  

I also created a new class named Image to encapsulate image informations.

    public class Image
    {
        #region Constructor

        public Image(int id, string previewPath, string thumbnailPath, bool loadMetadata, bool isRelativePaths)
        {
            Id = id;
            PreviewPath = previewPath;
            ThumbnailPath = thumbnailPath;
            IsRelativePaths = isRelativePaths;

            if (loadMetadata)
            {
                LoadMetadata();
            }
            else
            {
                Metadata = new List<Metadata>();
                IsMetadataLoaded = false;
            }
        }

        #endregion

        #region Properties

        public int Id { get; private set; }
        public string PreviewPath { get; private set; }
        public string ThumbnailPath { get; private set; }
        public bool IsRelativePaths { get; private set; }
        public IList<Metadata> Metadata { get; private set; }
        public bool IsMetadataLoaded { get; private set; }

        #endregion

        #region Methods

        public void LoadMetadata()
        {
            // If HttpContext is null and paths are relative, Throw an exception
            if (IsRelativePaths && HttpContext.Current == null)
            {
                throw new Exception("HttpContext is null, you cannot use relative paths.");
            }

            // Load the metadata from the preview file
            MediaInfo mediaInfo = new MediaInfo();
            mediaInfo.Open(IsRelativePaths ? HttpContext.Current.Server.MapPath(PreviewPath) : PreviewPath);
            mediaInfo.Option("Complete");
            string s = mediaInfo.Inform();

            // Parse the metadata
            var lines = s.Split('\n');
            Metadata = lines.
                Select(l => l.Split(':')).
                Where(a => a.Length == 2).
                Select(a => new Metadata(a[0].Trim(), a[1].Trim())).
                ToList();

            IsMetadataLoaded = true;
        }

        #endregion
    }
}

The LoadMetadata method loads the metadata of the image. MediaInfo outputs a string that contains all the informations of the image. I had to parse it to retrieve the metadata and set the Metadata property.

Data Binding

Now that we used a class for the images, the DataSource of the images Repeater changed. Here we parse the XML and load a collection of images without metadata. The metadata will be loaded lazily as you will see.

// Use ASP .NET Cache to boost paging performance
protected IList<Image> Images
{
    get
    {
        if (Cache["Images"] == null)
        {
            // Read images info from XML document
            XDocument xdoc = XDocument.Load(MapPath("/App_Data/Images.xml"));
            IList<Image> images = xdoc.Descendants("image").
                                    Select(i =>
                                        new Image(int.Parse(i.Attribute("id").Value),
                                                            i.Attribute("imageUri").Value,
                                                            i.Attribute("thumbnailUri").Value,
                                                            false,
                                                            true)).ToList();

            Cache["Images"] = images;
        }
        return (IList<Image>)Cache["Images"];
    }
}

In the version without metadata, I handled the preview view with jQuery so I used the hyperlink HTML tag because I didn't need to do postback. But, now that we want to feed another view (Metadata), we need to do a postback when the end-user clicks on the thumbnail in order to load the metadata in the view.

<asp:Repeater ID="RepeaterImages" runat="server" OnItemCommand="RepeaterImages_ItemCommand">
    <HeaderTemplate>
        <ul class="thumb">
    </HeaderTemplate>
    <ItemTemplate>
        <li>
            <asp:LinkButton ID="LinkButtonThumbnail" runat="server" CommandName="ShowPreview"
                CommandArgument='<%#DataBinder.Eval(Container.DataItem, "Id")%>'>
                <img src='<%#DataBinder.Eval(Container.DataItem, "ThumbnailPath")%>' alt="" />
            </asp:LinkButton>
        </li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater> 

I used a Repeater server control for the metadata view. I used the AlternatingItemTemplate in order to set up a custom layout for the even rows and a custom layout for the odd rows. An alternative would be to use the ItemTemplate only and set up the layout by using the CSS odd and even rules.

<div id="metadata" runat="server">
    <img src="Styles/Images/asset-green-16.png" alt="" />
    <span class="metadataHeader">Image information</span>
    <asp:Repeater ID="RepeaterMetadata" runat="server">
        <HeaderTemplate>
            <table cellpadding="0" cellspacing="0">
        </HeaderTemplate>
        <ItemTemplate>
            <tr>
                <td class="metadataName">
                    <%# DataBinder.Eval(Container.DataItem, "Key") %>
                </td>
                <td class="metadataValue">
                    <%# DataBinder.Eval(Container.DataItem, "Value") %>
                </td>
            </tr>
        </ItemTemplate>
        <AlternatingItemTemplate>
            <tr>
                <td class="metadataNameAlt">
                    <%# DataBinder.Eval(Container.DataItem, "Key") %>
                </td>
                <td class="metadataValueAlt">
                    <%# DataBinder.Eval(Container.DataItem, "Value") %>
                </td>
            </tr>
        </AlternatingItemTemplate>
        <FooterTemplate>
            </table>
        </FooterTemplate>
    </asp:Repeater>
</div> 

The header image that I used is under Creative Commons Attribution Share Alike license and is from SweetiePlus.

The method LoadImages has changed a little bit. Indeed, Now we use an IList<Image> as DataSource and the metadata view has to be hidden when the method is called.

private void LoadImages()
{
    // Populate the repeater control with the images collection
    // Indicate that the data should be paged
    // Set the number of images you wish to display per page
    // Set the PagedDataSource's current page
    var pds = new PagedDataSource
    {
        DataSource = Images,
        AllowPaging = true,
        PageSize = 9,
        CurrentPageIndex = CurrentPage - 1
    };

    LabelCurrentPage.Text = "Page " + CurrentPage + " of " + pds.PageCount;

    // Hide Previous or Next buttons if necessary
    ImageButtonPrevious.Visible = !pds.IsFirstPage;
    ImageButtonNext.Visible = !pds.IsLastPage;

    // Hide the metadata view
    metadata.Visible = false;

    // Bind data to the Images Repeater
    RepeaterImages.DataSource = pds;
    RepeaterImages.DataBind();
}

You will notice that I used the ItemCommand event in the images Repeater server control in order to feed the metadata view when the user clicks on the thumbnail. I also removed the jQuery code that shows the main view because now it is handled by the ItemCommand.

protected void RepeaterImages_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    if (e.CommandName.Equals("ShowPreview"))
    {
        // Retrieve the image Id from the command argument
        int imageId = int.Parse(e.CommandArgument.ToString());

        // Load the selected image
        Image image = Images.First(i => i.Id == imageId);

        // Load the Preview
        main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
        main_view.Visible = true;

        // Load the Metadata
        LoadMetadata(image);
        metadata.Visible = true;
    }
} 

The LoadMetadata method binds the metadata Repeater Lazily. In other words, we only load the metadata of the selected image when the user clicks on its thumbnail and once it is loaded we never load it again.

private void LoadMetadata(Image image)
{
    // Load the metadata lazily
    if (!image.IsMetadataLoaded)
    {
        image.LoadMetadata();
    }

    // Bind data to the Metadata Repeater
    RepeaterMetadata.DataSource = image.Metadata;
    RepeaterMetadata.DataBind();
} 

The paging didn't change.

Description of the Update 2

I thought It would be intersting to use Ajax in the page. With the UpdatePanel server control, we can display an animation while the metadata and the preview are being loaded.

In this update, I used the UpdatePanel server control, the UpdateProgress server control and updated both client side code and server side code.

Models

jQueryImageSlider/sc0.jpg

jQueryImageSlider/sc1.jpg

jQueryImageSlider/sc2.jpg

jQueryImageSlider/sc3.jpg

jQueryImageSlider/sc4.jpg

jQueryImageSlider/sc5.jpg

Client side

I used the UpdatePanel and UpdateProgress server controls in the page.

<form id="form1" 
      runat="server">
<asp:ScriptManager ID="ScriptManager1" 
                   runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanelContainer" 
                 runat="server">
    <ContentTemplate>
        <div class="container">
            <asp:Repeater ID="RepeaterImages" 
                          runat="server" 
                          OnItemCommand="RepeaterImages_ItemCommand">
                <HeaderTemplate>
                    <ul class="thumb">
                </HeaderTemplate>
                <ItemTemplate>
                    <li>
                        <asp:LinkButton ID="LinkButtonThumbnail" 
                                        runat="server" 
                                        CommandName="ShowPreview"
                                        CommandArgument='<%#DataBinder.Eval(Container.DataItem, "Id")%>'>
                            <img src='<%#DataBinder.Eval(Container.DataItem, "ThumbnailPath")%>' 
                                 alt="" />
                        </asp:LinkButton>
                    </li>
                </ItemTemplate>
                <FooterTemplate>
                    </ul>
                </FooterTemplate>
            </asp:Repeater>
            <div id="pager">
                <asp:Label ID="LabelCurrentPage" 
                           runat="server"></asp:Label>
                <asp:ImageButton ID="ImageButtonPrevious" 
                                 runat="server" 
                                 ImageUrl="Styles/Images/Arrow-Left-icon.png"
                                 OnClick="ImageButtonPrevious_Click" 
                                 CssClass="previous" />
                <asp:ImageButton ID="ImageButtonNext" 
                                 runat="server" 
                                 ImageUrl="Styles/Images/Arrow-right-icon.png"
                                 OnClick="ImageButtonNext_Click" 
                                 CssClass="next" />
            </div>
            <div id="main_view" 
                 runat="server">
            </div>
            <div id="metadata" 
                 runat="server">
                <img src="Styles/Images/asset-green-16.png" 
                     alt="" />
                <span class="metadataHeader">Image information</span>
                <asp:Repeater ID="RepeaterMetadata" 
                              runat="server">
                    <HeaderTemplate>
                        <table cellpadding="0" 
                               cellspacing="0">
                    </HeaderTemplate>
                    <ItemTemplate>
                        <tr>
                            <td class="metadataName">
                                <%# DataBinder.Eval(Container.DataItem, "Key") %>
                            </td>
                            <td class="metadataValue">
                                <%# DataBinder.Eval(Container.DataItem, "Value") %>
                            </td>
                        </tr>
                    </ItemTemplate>
                    <AlternatingItemTemplate>
                        <tr>
                            <td class="metadataNameAlt">
                                <%# DataBinder.Eval(Container.DataItem, "Key") %>
                            </td>
                            <td class="metadataValueAlt">
                                <%# DataBinder.Eval(Container.DataItem, "Value") %>
                            </td>
                        </tr>
                    </AlternatingItemTemplate>
                    <FooterTemplate>
                        </table>
                    </FooterTemplate>
                </asp:Repeater>
            </div>
        </div>
    </ContentTemplate>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress" 
                    runat="server" 
                    AssociatedUpdatePanelID="UpdatePanelContainer">
    <ProgressTemplate>
        <div class="throbber">
            <img src="Styles/Images/ajax-loader.gif" 
                 alt="" />
        </div>
    </ProgressTemplate>
</asp:UpdateProgress>
</form> 

Now that we used the UpdatePanel server control, we have to update the jQuery code. Indeed, the $(document).ready() function will not work properly. You can easily see that by using the $(document).ready() function and clicking on the next page for example, The fancy thumbnail hover effect disappears in the next page. But don't worry there is a solution to this issue. The general gist of it is to run the Javascript code after every UpdatePanel refresh. Thus, I used the pageLoad() Ajax function. pageLoad() is called after every UpdatePanel refresh and that is exactly what we were looking for. I strongly recommend reading this article regarding the Ajax pageLoad() function and the jQuery $(document).ready() function.

function pageLoad() {
    // Larger thumbnail preview 
    // hoverIntent
    window.$("ul.thumb li").hover(function () {
        window.$(this).css({ 'z-index': '10' });
        window.$(this).find('img').addClass('hover').stop()
            .animate({
                marginTop: '-110px',
                marginLeft: '-110px',
                top: '50%',
                left: '50%',
                width: '174px',
                height: '174px',
                padding: '20px'
            }, 200);

    }, function () {
        window.$(this).css({ 'z-index': '0' });
        window.$(this).find('img').removeClass('hover').stop()
            .animate({
                marginTop: '0',
                marginLeft: '0',
                top: '0',
                left: '0',
                width: '100px',
                height: '100px',
                padding: '5px'
            }, 400);
    });
} 

When the cursor is over an li we add the hover intent effect to the thumbnail by adding the hover CSS class to the img element and animating the image by using the animate jQuery function. When the cursor leaves the thumbnail we remove the hover CSS class and stop the animation of the image by using the animate jQuery function. The hover class is defined below.

ul.thumb li img.hover
{
    background: url(/Styles/Images/thumb_bg.png) no-repeat center center;
    border: none;
}

The CSS style of the ul element is defined below.

ul.thumb
{
    float: left;
    list-style: none;
    margin: 0;
    padding: 10px;
    width: 360px;
}

ul.thumb li
{
    margin: 0;
    padding: 5px;
    float: left;
    position: relative;
    width: 110px;
    height: 110px;
}

ul.thumb li img
{
    width: 100px;
    height: 100px;
    border: 1px solid #ddd;
    padding: 5px;
    background: #f0f0f0;
    position: absolute;
    left: 0;
    top: 0;
    -ms-interpolation-mode: bicubic;
}
It is also possible to set up this effect whithout using jQuery and just by using CSS3 transitions module.
I used the UpdateProgress server control to show an animation when the metadata and the preview are being loaded.
<asp:UpdateProgress ID="UpdateProgress" runat="server" AssociatedUpdatePanelID="UpdatePanelContainer">
    <ProgressTemplate>
        <div class="throbber">
            <img src="Styles/Images/ajax-loader.gif" alt="" />
        </div>
    </ProgressTemplate>
</asp:UpdateProgress> 
The animation gif that I used is generated automaticly from ajaxload. The CSS code is very basic. The throbber div is displayed in all the page with a transparent color and the ajaxload image is centered.
.throbber
{
    padding: 0;
    margin: 0;
    position: fixed;
    top: 0px;
    right: 0px;
    bottom: 0px;
    left: 0px;
    background: rgba(255, 255, 255, 0.5);
    z-index: 1;
}
.throbber img
{
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -9px;
    margin-left: -110px;
    z-index: 2;
}

Server side

I updated the Images Repeater ItemCommand's event to show you that the animation is working fine for one second. In a release version you should remove the Thread.Sleep. The animation would be very useful If you have a huge amount of data to process in the server side.
protected void RepeaterImages_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    if (e.CommandName.Equals("ShowPreview"))
    {

        // Show the Throbber one second
        // This line is here just to show you
        // That the Update Progress control is 
        // working fine
        // In a release version you should remove this line
        // The animation would be very useful
        // When your server call last more than 1 second
        Thread.Sleep(1000);

        // Retrieve the image Id from the command argument
        int imageId = int.Parse(e.CommandArgument.ToString());

        // Load the selected image
        Image image = Images.First(i => i.Id == imageId);

        // Load the Preview
        main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
        main_view.Visible = true;

        // Load the Metadata
        LoadMetadata(image);
        metadata.Visible = true;
    }
}  

Description of the Update 3

In this update I added a download button in the image's information box in order to offer to the end-user the ability to download the preview image.

I used an HttpHandler and persisted the current preview file path in the session state. I updated both server side code and client side code.

Models


jQueryImageSlider/scd1.JPG

jQueryImageSlider/scd2.JPG

jQueryImageSlider/scd3.JPG

jQueryImageSlider/scd4.JPG

jQueryImageSlider/scd5.JPG

jQueryImageSlider/scd6.JPG

jQueryImageSlider/scd7.JPG

Server side

I added a new property in the Default page called CurrentPreviewPath in order to persist the path of the current image in the session state.

protected string CurrentPreviewPath
{
    get
    {
        // Look for current image Path in Session State
        object o = Session["CurrentPreviewPath"];
        if (o == null) return string.Empty;
        return (string)o;
    }

    set { Session["CurrentPreviewPath"] = value; }
} 

The value of this property is updated when the end-user clicks on the thumbnail.

protected void RepeaterImages_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    if (e.CommandName.Equals("ShowPreview"))
    {
        // Retrieve the image Id from the command argument
        int imageId = int.Parse(e.CommandArgument.ToString());

        // Load the selected image
        Image image = Images.First(i => i.Id == imageId);

        // set the current preview path in the session state for the download HttpHandler
        CurrentPreviewPath = image.IsRelativePaths
                            ? MapPath(image.PreviewPath)
                            : image.PreviewPath;

        // Load the Preview
        main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
        main_view.Visible = true;

        // Load the Metadata
        LoadMetadata(image);
        metadata.Visible = true;
    }
} 
The HttpHandler is named DownloadHandler.

jQueryImageSlider/handler.JPG

In this handler, we look for the preview file path in the session state. You will notice that the HttpHandler implements IReadOnlySessionState interface in order to have the permission to read the session state.
public class DownloadHandler : IHttpHandler, IReadOnlySessionState
{
    #region Constants

    // Block of 1024 bytes
    const int _BLOCK_ = 1024;

    #endregion

    #region Properties

    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

    #endregion

    #region Methods

    public void ProcessRequest(HttpContext context)
    {
        // Look for the preview file path in the session state
        // You will notice that the handler implements IReadOnlySessionState interface
        // in order to have the permission to read the session state
        string imagePath = (string)context.Session["CurrentPreviewPath"];

        // Read file as binary values
        FileInfo imageFileInfo = new FileInfo(imagePath);
        using (FileStream imageFileStream = new FileStream(imagePath, FileMode.Open, 
                                                FileAccess.Read, FileShare.ReadWrite))
        using (BinaryReader br = new BinaryReader(imageFileStream))
        {
            try
            {
                // Get the Last update time and file name encoded
                string lastUpdate;
                var encodedData = new StringBuilder();
                encodedData.Append(HttpUtility.UrlEncode(imageFileInfo.Name, Encoding.UTF8));
                encodedData.Append(lastUpdate = imageFileInfo.LastWriteTimeUtc.ToString("r"));

                // Clean up the response and set the header informations
                context.Response.Clear();
                context.Response.Buffer = false;
                context.Response.AddHeader("Accept-Ranges", "bytes");
                context.Response.AppendHeader("ETag", "'" + encodedData + "'");
                context.Response.AppendHeader("Last-Modified", lastUpdate);
                context.Response.ContentType = "application/octet-stream";
                context.Response.AddHeader("Content-Disposition", "attachment;filename=" 
                                                                    + imageFileInfo.Name);
                context.Response.AddHeader("Content-Length", imageFileInfo.Length.ToString());
                context.Response.AddHeader("Connection", "Keep-Alive");

                // Set encoding to UTF8
                context.Response.ContentEncoding = Encoding.UTF8;

                // Send bytes
                br.BaseStream.Seek(0, SeekOrigin.Begin);

                // Dividing the data in _BLOCK_ bytes package
                long maxCount = (long)Math.Ceiling((imageFileInfo.Length + 0.0) / _BLOCK_);

                // Download in block of _BLOCK_ bytes
                for (long l = 0; l < maxCount && context.Response.IsClientConnected; l++)
                {
                    context.Response.BinaryWrite(br.ReadBytes(_BLOCK_));
                    context.Response.Flush();
                }
            }
            finally
            {
                // End the response
                context.Response.End();
            }
        }
    }

    #endregion

} 
To register the HttpHandler in the application we have to add these lines in the system.web section of the Web.config file.
<httpHandlers>
  <!--Register the DownloadHandler-->
  <add path="DownloadHandler.axd" verb="*" type="jQuery.ImageSlider.DownloadHandler, jQuery.ImageSlider" validate="true" />
</httpHandlers> 

Client Side

In the client side, I added a link element in the metadata div.
<a href="DownloadHandler.axd" class="download" title="Download">
    <img src="Styles/Images/Download-icon.png" alt="" />
</a> 
The download icon image that I used is from Icon Archive.

The button is placed at the top right of the box.
.download
{
    position: absolute;
    right: 0;
} 

Description of the Update 4 

I had a feedback on my blog talking about displaying the thumbnails titles. I thought It would be intersting to set up that.

In this update I added a Div container for each li. This Div contains the image thumbnail link button and a Div that contains the title of the thumbnail. I also added a property Title to the Image class. Its value is the name of the file without extension. You can of course add a title attribute to the image node in the images XML file and retrieve the value from there.

I updated both server side code and client side code.  

Models

jQueryImageSlider/sce1.JPG

jQueryImageSlider/sce2.jpg

jQueryImageSlider/sce3.jpg

jQueryImageSlider/sce4.jpg

jQueryImageSlider/sce5.jpg

jQueryImageSlider/sce6.jpg

jQueryImageSlider/sce7d.JPG

jQueryImageSlider/sce8d.JPG
jQueryImageSlider/sce9d.JPG

jQueryImageSlider/sce10.JPG

Server side

I added a property Title to the Image Class in order to store the image's title.

public class Image
{
    #region Constructors and Destructors

    public Image(
        int id, string title, string previewPath, string thumbnailPath, bool loadMetadata, bool isRelativePaths)
    {
        this.Id = id;
        this.Title = title;
        this.PreviewPath = previewPath;
        this.ThumbnailPath = thumbnailPath;
        this.IsRelativePaths = isRelativePaths;

        if (loadMetadata)
        {
            this.LoadMetadata();
        }
        else
        {
            this.Metadata = new List<Metadata>();
            this.IsMetadataLoaded = false;
        }
    }

    #endregion

    #region Public Properties

    public int Id { get; private set; }
    public bool IsMetadataLoaded { get; private set; }
    public bool IsRelativePaths { get; private set; }
    public IList<Metadata> Metadata { get; private set; }
    public string PreviewPath { get; private set; }
    public string ThumbnailPath { get; private set; }
    public string Title { get; private set; }

    #endregion

    #region Public Methods

    public void LoadMetadata()
    {
        // If HttpContext is null and paths are relative, Throw an exception
        if (this.IsRelativePaths && HttpContext.Current == null)
        {
            throw new Exception("HttpContext is null, you cannot use relative paths.");
        }

        // Load the metadata from the preview file
        var mediaInfo = new MediaInfo();
        mediaInfo.Open(
            this.IsRelativePaths ? HttpContext.Current.Server.MapPath(this.PreviewPath) : this.PreviewPath);
        mediaInfo.Option("Complete");
        string s = mediaInfo.Inform();

        // Parse the metadata
        string[] lines = s.Split('\n');
        this.Metadata =
            lines.Select(l => l.Split(':')).
            Where(a => a.Length == 2).
            Select(a => new Metadata(this.Id, a[0].Trim(), a[1].Trim())).
            ToList();

        this.IsMetadataLoaded = true;
    }

    #endregion
}

Thus, I updated the Images collection property in the code-behind of the Default page. I used the name of the image file without extension as a title. You can of course add a title attribute to the image node of the Images.xml file and use it instead.

// Use ASP .NET Cache to boost paging performance
private IList<Image> Images
{
    get
    {
        if (this.Cache["Images"] == null)
        {
            // Read images info from XML document
            XDocument xdoc = XDocument.Load(this.MapPath("/App_Data/Images.xml"));

            IList<Image> images = (from xe in xdoc.Descendants("image")
                                    let id = int.Parse(xe.Attribute("id").Value)
                                    let thumbnailUri = xe.Attribute("thumbnailUri").Value
                                    let previewUri = xe.Attribute("imageUri").Value
                                    let tite = Path.GetFileNameWithoutExtension(previewUri)
                                    select new Image(id, tite, previewUri, thumbnailUri, false, true)).
                                    ToList();

            this.Cache["Images"] = images;
        }
        return (IList<Image>)this.Cache["Images"];
    }
}

This is the only things that changed in the sever side code.

Client Side

I used a div container for the image in the RepeaterImages server control in order to show the image and its title on the client side. You can of course add any controls you want in this div depending on your needs.

<asp:Repeater ID="RepeaterImages" runat="server" OnItemCommand="RepeaterImages_ItemCommand">
    <HeaderTemplate>
        <ul class="thumb">
    </HeaderTemplate>
    <ItemTemplate>
        <li>
            <div class="imageContainer">
                <div class="imageTitle">
                    <%#DataBinder.Eval(Container.DataItem, "Title")%>
                </div>
                <asp:LinkButton ID="LinkButtonThumbnail" runat="server" CommandName="ShowPreview"
                                CommandArgument='<%#DataBinder.Eval(Container.DataItem, "Id")%>'>
                    <img src='<%#DataBinder.Eval(Container.DataItem, "ThumbnailPath")%>' alt="" />
                </asp:LinkButton>
            </div>
        </li>
    </ItemTemplate>
    <FooterTemplate>
    </ul>
    </FooterTemplate>
</asp:Repeater>

This is the only thing that changed in the Default.aspx page. The CSS code is described below.

ul.thumb
{
    float: left;
    list-style: none;
    margin: 0;
    padding: 10px;
    width: 360px;
}

ul.thumb li
{
    margin: 0;
    padding: 5px;
    float: left;
    position: relative;
    width: 110px;
    height: 110px;
}

ul.thumb li .imageContainer
{
    width: 100px;
    height: 100px;
    text-align: center;
    position: absolute;
    left: 0;
    top: 0;
    padding: 5px;
}

ul.thumb li .imageContainer .imageTitle
{
    margin: 0;
    width: 100%;
    white-space: nowrap;
    overflow: hidden;
}

ul.thumb li .imageContainer img
{
    width: 90px;
    height: 90px;
    background: #f0f0f0;
    -ms-interpolation-mode: bicubic;
    padding: 5px;
}

/* Change the image container background on mouse hover */
ul.thumb li .imageContainer.hover
{
    background: url(/Styles/Images/thumb_bg.png) no-repeat center center;
    border: none;
}

/* Change the backgournd color of the Title on mouse hover*/
ul.thumb li .imageContainer .imageTitleHover
{
    background: #f0f0f0;
}

/* Resize the thumbnail image on mouse hover*/
ul.thumb li .imageContainer .imageHover
{
    width: 164px;
    height: 150px;
    background: #f0f0f0;
    -ms-interpolation-mode: bicubic;
    padding: 5px;
}

I also updated the jQuery code in order to animate the thumbnail on hover client side event.

$(document).ready(function() {
    // Larger thumbnail preview, hover intent 
    $("ul.thumb li").hover(function() {
        $(this).css({ 'z-index': '10' });
        $(this).find('.imageTitle').addClass("imageTitleHover");
        $(this).find('.imageContainer').find("img").addClass("imageHover");
        $(this).find('.imageContainer').addClass('hover').stop()
            .animate({
                    marginTop: '-110px',
                    marginLeft: '-110px',
                    top: '50%',
                    left: '50%',
                    width: '174px',
                    height: '174px',
                    padding: '20px'
                }, 200);

    }, function() {
        $(this).css({ 'z-index': '0' });
        $(this).find('.imageTitle').removeClass("imageTitleHover");
        $(this).find('.imageContainer').find("img").removeClass("imageHover");
        $(this).find('.imageContainer').removeClass('hover').stop()
            .animate({
                    marginTop: '0',
                    marginLeft: '0',
                    top: '0',
                    left: '0',
                    width: '100px',
                    height: '100px',
                    padding: '5px'
                }, 400);
    });
});   

Description of the Update 5

I had a feedback talking about adding more transition effects between the slides such as the Menucool Image Slider.

It is possible to add this kind of visual effects to the preview side. Thus, I updated the original version in order to illustrate how visual effects can be added in the preview side. 

I used jQuery for the animation and updated the client side jQuery code. When the end-user clicks on a thumbnail, and once the preview image is loaded, It is animated with slideToggle function. Just a simple animation to illustrate where and how to add visual effects in the preview side.
 // Swap Image on Click
$('ul.thumb li a').click(function () {
    var mainImage = $(this).attr("href"); //Find Image Name

    // Once the image has been loaded, animate the preview
    $('<img>', { src: mainImage }).bind('load', function () {
        $(this).css({ 'display': 'none' });
        $('#main_view').empty();
        $('#main_view').append(this);
        $('#main_view').show();
        $(this).slideToggle('slow');
    });

    return false;
}); 

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0

About the Author

Akram El Assas

Architect
Mediatvcom
France France

Member

Follow on Twitter Follow on Twitter


Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
SuggestionIs it possible to add more transition effects such as the image slider of www.menucool.com PinmemberJohn Wcb5:27 2 Apr '12  
GeneralRe: Is it possible to add more transition effects such as the image slider of www.menucool.com PinmemberAkram El Assas8:44 2 Apr '12  
GeneralMy 5 PinmemberShahriar Iqbal Chowdhury21:09 31 Mar '12  
GeneralRe: My 5 PinmemberAkram El Assas6:12 1 Apr '12  
GeneralMy vote of 5 Pinmembermanoj kumar choubey19:52 4 Mar '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas11:20 7 Mar '12  
Questioncan i use for my website Pinmemberductri22:50 30 Jan '12  
AnswerRe: can i use for my website PinmemberAkram El Assas0:42 31 Jan '12  
GeneralMy vote of 5 Pinmembertigercont21:41 28 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas11:26 29 Jan '12  
GeneralMy vote of 5 Pinmemberzabahey19:14 28 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas11:26 29 Jan '12  
GeneralMy vote of 5 Pinmemberguptasanjay5617:48 19 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas5:53 20 Jan '12  
GeneralMy vote of 5 PinmvpKanasz Robert1:04 18 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas10:19 19 Jan '12  
AnswerRe: My vote of 5 PinmvpKanasz Robert12:11 19 Jan '12  
GeneralMy vote of 5 Pinmemberhoernchenmeister1:55 16 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas7:10 16 Jan '12  
GeneralRe: My vote of 5 Pinmemberhoernchenmeister20:46 16 Jan '12  
GeneralMy vote of 5 PinmemberNagy Vilmos3:15 9 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas6:38 9 Jan '12  
GeneralMy vote of 5 Pinmemberitaitai22:15 24 Dec '11  
GeneralRe: My vote of 5 PinmemberAkram El Assas11:52 4 Jan '12  
GeneralMy vote of 5 PinmemberTech Code Freak19:13 24 Dec '11  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120528.1 | Last Updated 2 Apr 2012
Article Copyright 2011 by Akram El Assas
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid