Building A Web App – Slide Show Area

Note: This is a continuation of a series of posts on the design and development of a web application for a non-profit organization. Previous posts:

This blog post will add a slide show to the application that we are building. Images are a great way to get people’s attention. Here is a screen shot and a screen cast of the working slide show.

image

 

Requirements and Design

There are two main requirements that are going to drive the design of this slide show.

  • Each photo shown will have a link associated with it that will direct the user to a new page.
  • The photos displayed in the slide show must be configurable. This is key because, I want to empower the end user to be able to make changes to the slide show content.

Because of these, I am choosing to let my ASP.NET MVC view render out the HTML for the slideshow.

Configuration

Currently, the configuration is done manually. However, visual tooling can be added that would hide the complexity. By convention the photos and a configuration file are expected in a “Slides” folder that is residing inside the “Content” folder as shown below:

image All slideshow photos should be placed in this folder. The “SlideConfig.xml” file contains the configuration for each photo. An example of this file content is shown below:

<?xml version="1.0"?>
<SlideConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Slides>
    <Slide>
      <ImageUrl>photo1.gif</ImageUrl>
      <ImageAlt>photo1</ImageAlt>
      <AnchorUrl>http://google.com</AnchorUrl>
      <IsShown>true</IsShown>
    </Slide>
    <Slide>
      <ImageUrl>photo2.gif</ImageUrl>
      <ImageAlt>photo2</ImageAlt>
      <AnchorUrl>http://google.com</AnchorUrl>
      <IsShown>true</IsShown>
    </Slide>
    <!-- others -->
  </Slides>
</SlideConfig>

The options for each slide are the photo file name, a string for the HTML “alt” attribute of the “img” tag, a URL to redirect to when the photo is clicked, and a flag indicating whether to include the photo in the slide show.

Configuration File Reader

The configuration file contents will be deserialized from file into the following objects:

public class Slide
{
    public string ImageUrl { get; set; }
    public string ImageAlt { get; set; }
    public string AnchorUrl { get; set; }
    public bool IsShown { get; set; }
}

public class SlideConfig
{
    public List<Slide> Slides { get; set;}

    public SlideConfig()
    {
        Slides = new List<Slide>();
    }


    public static SlideConfig ReadConfig(string configFile)
    {
        return XmlData.DeserializeFromFile<SlideConfig>(configFile);
    }

    public static void SaveConfig(SlideConfig slideConfig, string configFile)
    {
        XmlData.SerializeToFile(slideConfig, configFile);
    }
}

The “Slide” class holds the configuration for each photo. The “SlideConfig” class provides two static methods to read and save the configuration. I have a generic xml serializer / deserializer that I use for this purpose.

public static class XmlData
{
    public static T DeserializeFromFile<T>(string path) where T : class
    {
        Condition.Requires(path, "path")
            .IsNotNullOrEmpty()
            .FileExists();

        FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
        T xmlSerializableObject = DeserializeFromStream<T>(stream);
        stream.Close();

        return xmlSerializableObject;
    }

    public static T DeserializeFromStream<T>(Stream stream)
    {
        Condition.Requires(stream, "stream")
            .IsNotNull();

        XmlSerializer serializer = new XmlSerializer(typeof(T));
        T xmlSerializableObject = (T)serializer.Deserialize(stream);

        return xmlSerializableObject;
    }

    public static void SerializeToFile<T>(T xmlSerializableObject, string path) where T : class
    {
        Condition.Requires(xmlSerializableObject, "xmlSerializableObject")
            .IsNotNull();
        Condition.Requires(path, "path")
            .IsNotNullOrEmpty();

        string folder = Path.GetDirectoryName(path);
        if (!Directory.Exists(folder))
        {
            Directory.CreateDirectory(folder);
        }

        FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write);
        SerializeToStream(xmlSerializableObject, stream);
        stream.Close();
    }

    public static void SerializeToStream<T>(T xmlSerializableObject, Stream stream) where T : class
    {
        Condition.Requires(xmlSerializableObject, "xmlSerializableObject")
            .IsNotNull();
        Condition.Requires(stream, "stream")
            .IsNotNull();

        XmlSerializer serializer = new XmlSerializer(typeof(T));

        serializer.Serialize(stream, xmlSerializableObject);
    }
}

If you are not using a library for testing your input parameters, you should take a look at the Cutting Edge Conditions library. It is really nice library and provides a fluent interface to enforce constraints on your input parameters. I previously posted on how it uses C# chaining.

Model

The ASP.NET model for the slide show is currently pretty lean and is shown here:

public class HomeViewModel
{
    public SlideConfig SlideConfig { get; set; }

}

The only model data for the home controller view-model is currently the slideshow configuration. Later we will be able to add additional data as other sections of our home view are developed.

Controller

The ASP.NET “Index” action method for the home controller (the slideshow is a part of the home view) is shown below:

public ActionResult Index()
{
    HomeViewModel hvm = new HomeViewModel();

    // Read the slide config file.
    //
    string configFile = Request.MapPath("~/Content/Slides/SlideConfig.xml");
    hvm.SlideConfig = SlideConfig.ReadConfig(configFile);

    return View(hvm);
}

The controller simply creates a home view-model and passes it to the view.

View

The view for the “Index” action method of the home controller is shown next:

<div class="main">

    <% Html.RenderPartial("_SlideShow", Model.SlideConfig); %>

</div>

<div class="sidebar">
    sidebar
</div>

<div class="clear"></div>

This view has to main HTML “div” regions that divide the main content area into a main section and a sidebar. The CSS to accomplish position these elements is as follows:

.main
{
    clear: both;
    float: left;
    width: 725px;
}
.sidebar
{
    float: right;
    width: 225px;
}

The slideshow content has been encapsulated into an ASP.NET MVC partial view. Notice the “SlideConfig” object is being passed into the partial view to use as its model. The “SlideConfig” typed partial view for the slideshow is shown next:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Heartland.Code.SlideConfig>" %>

<div id="slides">

    <ul id="slideshow">

    <%    foreach (Heartland.Code.Slide slide in Model.Slides)
        {
            if (slide.IsShown)
            { %>
                <li>
                    <a href="<%=Html.Encode(slide.AnchorUrl)%>" title="<%=Html.Encode(slide.ImageAlt)%>">
                        <img alt="<%=Html.Encode(slide.ImageAlt)%>" src="Content/Slides/<%=Html.Encode(slide.ImageUrl)%>" />
                    </a>
                </li>
    <%        }
        } %>

    </ul>

    <div id="slidenav">

    <%    int currentIndex = 1;
        foreach (Heartland.Code.Slide slide in Model.Slides)
        {
            if (slide.IsShown)
            { %>
                <a href="#">
                    <%= currentIndex++ %>
                </a>
    <%        }
        } %>

    </div>

</div>

This is where the HTML elements for the slideshow are generated. The slideshow content has two main HTML “div” tags. The element with “slideshow” as the ID contains the slideshow content. This content is an unordered list where each list element is an anchor surrounding the image. The element with “slidenav” as the ID defines the navigation boxes in the lower-right hand corner.

The CSS that styles the slideshow elements is shown below:

#slides
{
    background-color: #fff;
    margin: 0 0 10px 0;
    position: relative;
}

#slides ul
{
    list-style: none;
    height: 288px;
    width: 723px;
}

#slides ul li
{
    display: none;
}

#slides ul li a img
{
    border-radius: 10px;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
}

#slides

#slidenav
{
    display: block;
    float: right;
    font-weight: bold;
    position: absolute;
    right: 5px;
    bottom: 5px;
}

#slidenav a
{
    background-color: #8dc3e9;
    border: 1px solid #00477f;
    color: #000;
    display: inline-block;
    margin: 0 5px 0 0;
    padding: 1px 5px;
    text-decoration: none;
}

#slidenav a.activeslide
{
    background-color: #4c88be;
    border: 1px solid #00477f;
    color: #fff;
}

The colors in the above CSS are selections from the theme that we previously selected. The width and height of the HTML “ul” element maintains the size as the “li” elements are hidden and displayed. Notice all the “li” elements are initially not displayed. The displaying will be done using jQuery in a bit. The “slidenav” element is displayed absolutely with respect to the “slide” element. This allows the navigational elements to be easily positioned. Finally, a class for “activeslide” is defined. This is used to highlight the current slide in the navigational list.

The following JavaScript is used to bring the slideshow to life. First, all the slide show and navigational elements are selected and stored.

<script type="text/javascript">
    var slideItems = undefined;
    var slideNavs = undefined;
    var currentIndex = -1;
    var slideInteval = undefined;

    $(document).ready(function() {
        slideItems = $("#slideshow li");
        slideNavs = $("#slidenav a");

        showNextSlide();
        slideInterval = setInterval(showNextSlide, 7000);

        $("#slidenav a").click(function() {
            var index = $(this).html();

            hideSlide(currentIndex, function() {

                currentIndex = index - 1;
                showSlide(currentIndex);

            });
        });

        $("#slidenav").hover(
            function() {
                if (slideInterval != undefined) {
                    clearInterval(slideInterval);
                }
            },
            function() {
                slideInterval = setInterval(showNextSlide, 7000);
            }
        );
    });

    function showNextSlide() {
        hideSlide(currentIndex, function() {

            currentIndex++;
            if (currentIndex >= slideItems.size()) {
                currentIndex = 0;
            }

            showSlide(currentIndex);

        });
    }

    function hideSlide(index, callback) {
        if (currentIndex >= 0) {
            $(slideNavs[index]).removeClass("activeslide");
            $(slideItems[index]).fadeOut(10);
        }

        callback();
    }

    function showSlide(index) {
        $(slideNavs[index]).addClass("activeslide");
        $(slideItems[index]).fadeIn(1000);
    }

</script>

Next an interval timer is started that is triggered every seven seconds. This timer, simply shows the next slide. The “click” event handler is wired up for each of the navigational elements to show the slide corresponding to the clicked element. A “hover” is also wired up to suspend the slideshow timer whenever the mouse enters the region and restart it upon exit. This allows a better user experience.

Summary

Building the a slideshow component turns out to be not that difficult. This implementation is manually configurable. It will not be difficult to add visual tooling that is accessible by administrators to allow configuration via a web form.

No Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

*