Introduction
Viewing sets of images such as albums is a common functionality of many web applications. In this article we will take how to quickly build such functionality with Unobtrusive AJAX in an ASP.NET MVC 3 environment. Specifically, we want our application to have this functionality:
- Scroll bar to view the thumbnail images in a set or album.
- Main portion of the page that let's you see the selected image.
- The selected thumbnail image needs to be highlighted.
Background
The tricky part in this task is that we need to be able to dynamically build clickable thumbnail images that make the appropriate action call to load the selected image.
Using the Code
There are many moving parts to get our album page working. We need to set up our model followed by the controller and the views. But first let's take care of the references and CSS classes. In your _Layout page make sure your are referencing the following jQuery files:
- jquery-1.5.1.min.js
- jquery.unobtrusive-ajax.min.js
In the web.config file you must also have an application setting allowing the use of unobtrusive AJAX:
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
In your CSS class we need to have a style to highlight the clicked image.
img.highlighted {
border: 2px solid blue;
}
Models
First class we need to add to our models folder is the HTML helper for our action links. This code will be used later to create HTML img tags for the thumbnails.
public static class ImageActionLinkHelper
{
public static IHtmlString ImageActionLink(this AjaxHelper helper, string imageUrl, string altText, string titleText, string actionName, object routeValues, AjaxOptions ajaxOptions)
{
var builder = new TagBuilder("img");
builder.MergeAttribute("src", imageUrl);
builder.MergeAttribute("alt", altText);
builder.MergeAttribute("title", titleText);
var link = helper.ActionLink("[replaceme]", actionName, routeValues, ajaxOptions);
return MvcHtmlString.Create(link.ToString().Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing)));
}
}
Next up is our Image model which will contain the image objet itself along with the name of the image. Feel free to add any other fields you think should go along with the image. Most important methods here are to retreive the images. In the demo example I'm retrieving files from the local hard drive, but you can easily adapt this code to retieve from a DB. The other important function returns the thumbnail, minimizing the network traffic between server and client to boost performance.
public class YourImage
{
public string ImageID { get; set; }
public byte[] ImageBytes { get; set; }
public static byte[] GetImage(YourImage yourImage, bool isThumbnail)
{
byte[] image = yourImage.ImageBytes;
if (isThumbnail)
{
MemoryStream ms = new MemoryStream(image);
Image returnImage = Image.FromStream(ms);
returnImage = returnImage.GetThumbnailImage(100, 100, ThumbnailCallback, IntPtr.Zero);
MemoryStream output = new MemoryStream();
returnImage.Save(output, ImageFormat.Jpeg);
return output.ToArray();
}
return image;
}
public static List<YourImage> GetImageList(string folder)
{
List<YourImage> imList = new List<YourImage>();
string[] fileEntries = Directory.GetFiles(folder);
foreach (string picturePath in fileEntries)
{
YourImage urImage = new YourImage();
string pictureName = picturePath.Substring(picturePath.LastIndexOf("\\") + 1, (picturePath.Length - picturePath.LastIndexOf("\\") - 1));
urImage.ImageID = pictureName;
Image currImage = Image.FromFile(picturePath);
MemoryStream output = new MemoryStream();
currImage.Save(output, ImageFormat.Jpeg);
urImage.ImageBytes = output.ToArray();
imList.Add(urImage);
}
return imList;
}
}
Controller
Next up is the code for the controller, which is fairly small, so that thumbnail list can be pulled as well as the selected image.
public class ImageController : Controller
{
public ActionResult Index()
{
string folder = ConfigurationManager.AppSettings["ImagesFolder"];
List<YourImage> imageList = YourImage.GetImageList(folder);
Session["ImageList"] = imageList;
YourImage currImage = imageList.FirstOrDefault();
TempData["CurrentImage"] = currImage;
return View(imageList);
}
public ActionResult GetYourImage(string imageName, bool isThumbnail)
{
List<YourImage> shlist = (List<YourImage>)Session["ImageList"];
YourImage shi = YourImage.GetYourImageFromList(imageName, shlist);
byte[] image = YourImage.GetImage(shi, isThumbnail);
if (image == null)
return null;
else
return File(image, "image/jpg", imageName);
}
public ActionResult ImageDetails(string imageName)
{
List<YourImage> shlist = (List<YourImage>)Session["ImageList"];
YourImage shi = YourImage.GetYourImageFromList(imageName, shlist);
return PartialView(shi);
}
}
Views
First up is the Index view where most of the content is held, the list of the thumbnails as well as the selected image section. First up we need to place some jQuery to toggle between the selected thumbnails. You can add this to the top of the Index view.
$(function () {
$('#select-form img').click(
function () {
$('#image-value').val($(this).attr('data-value'));
$('#select-form img').removeClass('highlighted');
$(this).addClass('highlighted');
});
});
The most critical piece is the thumbnail links that will be generated inside the for loop as you iterate over each thumbnail. Here the action links are nested as we need to generate a source URL for the image as well as the hyperlink to call the action to update the selected image. The AjaxOptions
instruct where the action result will render, replacing the html within the currentFullImage <div>
This is the part where our custom HTML extension comes into play. Make sure the name of the div matches the UpdateTargetId
parameter.
@Ajax.ImageActionLink(@Url.Action("GetYourImage", "Image", new { imageName = item.ImageID, isThumbnail = true }),
"no image",
"NoImageTitle",
"ImageDetails",
new { imageName = item.ImageID },
new AjaxOptions { UpdateTargetId = "currentFullImage", InsertionMode = InsertionMode.Replace })
The next view is the ImageDetails view where the selected image is displayed.
@model ImageAlbumDemo.Models.YourImage
<br/>
<div id="currentFullImage">
<img src="@Url.Action("GetYourImage", "Image", new { imageName = Model.ImageID, isThumbnail = false })"
alt="No Image" width="100%" height="100%" id="imgMain" />
</div>
Now we are ready to roll, here is the end result!
Points of Interest
Now that the app running, take a closer look at the HTML that has been generated by the ImageActionLink
function. The thumbnail image object is located inside the hyperlink element <a>.
<a data-ajax="true" data-ajax-mode="replace" data-ajax-update="#currentFullImage" href="http://www.codeproject.com/Image/ImageDetails?imageName=clip-art-circus-465619.jpg">
<img alt="no image" src="http://www.codeproject.com/Image/GetYourImage?imageName=clip-art-circus-465619.jpg&isThumbnail=True" title="NoImageTitle" />
</a>
History
No updates as of yet.