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



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()
{
var pds = new PagedDataSource
{
DataSource = ImagesDataView,
AllowPaging = true,
PageSize = 9,
CurrentPageIndex = CurrentPage - 1
};
LabelCurrentPage.Text = "Page " + CurrentPage + " of " + pds.PageCount;
ImageButtonPrevious.Visible = !pds.IsFirstPage;
ImageButtonNext.Visible = !pds.IsLastPage;
RepeaterImages.DataSource = pds;
RepeaterImages.DataBind();
}
The current page number is persisted in the ViewState.
public int CurrentPage
{
get
{
object o = ViewState["CurrentPage"];
if (o == null) return 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)
{
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)
{
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)
{
CurrentPage++;
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)
{
CurrentPage--;
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


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 (IsRelativePaths && HttpContext.Current == null)
{
throw new Exception("HttpContext is null, you cannot use relative paths.");
}
MediaInfo mediaInfo = new MediaInfo();
mediaInfo.Open(IsRelativePaths ? HttpContext.Current.Server.MapPath(PreviewPath) : PreviewPath);
mediaInfo.Option("Complete");
string s = mediaInfo.Inform();
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.
protected IList<Image> Images
{
get
{
if (Cache["Images"] == null)
{
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()
{
var pds = new PagedDataSource
{
DataSource = Images,
AllowPaging = true,
PageSize = 9,
CurrentPageIndex = CurrentPage - 1
};
LabelCurrentPage.Text = "Page " + CurrentPage + " of " + pds.PageCount;
ImageButtonPrevious.Visible = !pds.IsFirstPage;
ImageButtonNext.Visible = !pds.IsLastPage;
metadata.Visible = false;
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"))
{
int imageId = int.Parse(e.CommandArgument.ToString());
Image image = Images.First(i => i.Id == imageId);
main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
main_view.Visible = true;
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)
{
if (!image.IsMetadataLoaded)
{
image.LoadMetadata();
}
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

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() {
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"))
{
Thread.Sleep(1000);
int imageId = int.Parse(e.CommandArgument.ToString());
Image image = Images.First(i => i.Id == imageId);
main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
main_view.Visible = true;
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

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
{
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"))
{
int imageId = int.Parse(e.CommandArgument.ToString());
Image image = Images.First(i => i.Id == imageId);
CurrentPreviewPath = image.IsRelativePaths
? MapPath(image.PreviewPath)
: image.PreviewPath;
main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
main_view.Visible = true;
LoadMetadata(image);
metadata.Visible = true;
}
} The
HttpHandler is named
DownloadHandler.

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
const int _BLOCK_ = 1024;
#endregion
#region Properties
public bool IsReusable
{
get
{
return true;
}
}
#endregion
#region Methods
public void ProcessRequest(HttpContext context)
{
string imagePath = (string)context.Session["CurrentPreviewPath"];
FileInfo imageFileInfo = new FileInfo(imagePath);
using (FileStream imageFileStream = new FileStream(imagePath, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite))
using (BinaryReader br = new BinaryReader(imageFileStream))
{
try
{
string lastUpdate;
var encodedData = new StringBuilder();
encodedData.Append(HttpUtility.UrlEncode(imageFileInfo.Name, Encoding.UTF8));
encodedData.Append(lastUpdate = imageFileInfo.LastWriteTimeUtc.ToString("r"));
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");
context.Response.ContentEncoding = Encoding.UTF8;
br.BaseStream.Seek(0, SeekOrigin.Begin);
long maxCount = (long)Math.Ceiling((imageFileInfo.Length + 0.0) / _BLOCK_);
for (long l = 0; l < maxCount && context.Response.IsClientConnected; l++)
{
context.Response.BinaryWrite(br.ReadBytes(_BLOCK_));
context.Response.Flush();
}
}
finally
{
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>
-->
<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




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 (this.IsRelativePaths && HttpContext.Current == null)
{
throw new Exception("HttpContext is null, you cannot use relative paths.");
}
var mediaInfo = new MediaInfo();
mediaInfo.Open(
this.IsRelativePaths ? HttpContext.Current.Server.MapPath(this.PreviewPath) : this.PreviewPath);
mediaInfo.Option("Complete");
string s = mediaInfo.Inform();
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.
private IList<Image> Images
{
get
{
if (this.Cache["Images"] == null)
{
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;
}
ul.thumb li .imageContainer.hover
{
background: url(/Styles/Images/thumb_bg.png) no-repeat center center;
border: none;
}
ul.thumb li .imageContainer .imageTitleHover
{
background: #f0f0f0;
}
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() {
$("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.
$('ul.thumb li a').click(function () {
var mainImage = $(this).attr("href");
$('<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;
});