How Do I Display a Slider?

Modified on 2010/09/14 18:31 by James Chambers — Categorized as: Uncategorized

Problem

I need to take an input in a certain range. Rather than just give a user a text box and wait for a validation failure when they are out-of-range, I want a user experience where they can interactively select values that are gauranteed to be valid in my model. How do I achieve this?

Solution

Fear not, tireless user-interface improver! jQuery UI has a slider control with minimum and maximum values, allowing users to select a value - or range of values - that will work for your application.

Getting Good Values

When I first signed up for driver's eduction I had to fill out a form which asked for my age. I could've written "rooster" in that box and no one could have stopped me. Fast-forward to today, and we're still largely giving users more freedom than they should have to deal with when it comes to data input.

If a 14 year-old isn't allowed to drive, why allow 8 as an age on your form? Let's look at the basic implementation of the slider, specifying only the minimum and maximum that a person is allowed to select.

<div id="age-slider"></div>

And the script is a single call to slider():
$("#age-slider").slider({ min: 16, max: 125 });

When you're ready to get the value out, we call slider again and pass in the text 'value'.
var selectedValue = $("#age-slider").slider('value');

Image

Nifty! There are four modes to the slider in each of the horizontal and vertical orientations.
Image

Functionally, there is no difference between the default, min and max modes; they are essentially the same with a slight difference in display. The colored bar - absent from the default mode - is tied to the low end for min and the high end on max. You still retrieve the value the same way.

For the range mode of the slider, we would need to use code similar to the following to extract the values:
var lowend = $("#range-slider").slider("values", 0);
var highend = $("#range-slider").slider("values", 1);

Want to see that range slider in action? I thought you'd never ask...

Using a Range Slider with the MVC Framework

So we've got this groovy slider and we've know that jQuery and the MVC Framework were built with Ajax in mind, so let's get the two working together.

Image

In this sample we'll look at a slider that drives a selection of snacks. As the user manipulates the slider we make Ajax calls to our controller which returns a partial view. The partial view is filtered by the two input values of the slider and data within the range specified by the user is returned from our repository.

<h2>Digital Snack Shack</h2>

<p>Welcome to the Digital Snack Shack!</p>

<p>We have a wide assortment of snacks to suit any budget. Use the slider to adjust the price range of items you'd like to see.</p>

<div id="snack-price"></div>

<div id="snack-list"> <% Html.RenderAction("ShowSnacks"); %> </div>

The placeholder snack-price DIV will become our slider, while snack-list will be used to refresh the list of snacks from our partial view. On page load, we kick things off by in-lining the RenderAction() call to ShowSnacks.

<script language="javascript" type="text/javascript">
$(function () {
    $("#snack-price").slider({
        range: true,
        min: 0,
        max: 3,
        values: [.3, 1.5],
        change: function (event, ui) {...}, 
	step: 0.05
    });
});

</script>

You'll notice there is an anonymous function for the change event of the slider, which we'll come back to in a moment. The other options define the minimum and maximum values of the slider, set it up in range mode and set the starting values. Finally, we make our slider 'step' in 0.05 increments, equivalant in our use to a nickel. All of this is wrapped in our jQuery document ready block.

The change event goes a little something like this:
change: function (event, ui) {
    var min = ui.values[0];
    var max = ui.values[1];

$.ajax({ url: '/slider/snack/ShowSnacks', data: ({ minPrice: min, maxPrice: max }), success: function (data) { $("#snack-list").html(data); } }) }

Our script captures the values of the slider and makes the Ajax request to our action, passing along those values. With a successful result, the partial view returned is loaded into the snack-list DIV as the new content.

Our partial view is very straightforward, requiring only a loop to output the snacks in a table:
<table>
    <tr>
        <th>Snack Name</th>
        <th>Price</th>
    </tr>
<% foreach (var item in Model) { %>
    <tr>
        <td><%: item.Title %></td>
        <td><%: String.Format("{0:F}", item.Price) %></td>
    </tr>
<% } %>
</table>

Try Me!

Cranking Up the MVC-ish-ness

As I used the slider I got to thinking about the kinds of problems that it solves in user interfaces. The slider is a way of giving the user a pre-emptive validation: you can't pick a bad value.

Now, ASP.NET has grown some very rich concepts for validation, including client-side scripts, server-side checking in the MVC Framework, and even model-level attributes to make it all tick.

Couldn't we leverage those concepts and make something interesting happen?

What if we had a class that used the RangeAttribute like this:
public class Course
{
    public string Title { get; set; }
    public string Description { get; set; }

[Range(8, 75)] public int StudentCount { get; set; } }

A class size that is restricted to a range of 8-75 students. Isn't that a good candidate for a slider? Let's assume we were looking for something that looked as follows:

Image

How do we get to a point where we can render a form for the user, where we don't have to do any more than this:
<form method="post" action="<%: Url.Action("Create") %>">
    <fieldset id="course-creator">
        <legend>Course Details</legend>
        <%= Html.EditorForModel() %>
        <input type="submit" value="Add Course" />
    </fieldset>
</form>

That's right, I want to generate the entire form with one call to EditorForModel(). Let's do it.

Gettin' Our MVC On

Let's take a quick inventory of what is required here:

The RangeHelper Custom HtmlHelper Extension Methods

We will need an easy way to access the minimum and maximum values on our class so that we can inject those values into the slider. That means using a bit of reflection (to get the values out) and coming up with an HtmlHelper extension method (to write out values to the client).

It's really not much work to get it to tick. Create a folder in your project called Helpers and create a class called RangeHelper. Next, add your extension methods for the HtmlHelper class. In this sample method, which exposes the minimum, I've added exactly no robustness whatsoever: if anything goes wrong it returns zero. For a production system you'd want better input testing and a better way to surface the error to the caller.
public static string RangeMinimum(this HtmlHelper helper, Type type, string member)
{
    try
    {
        MemberInfo mi = type.GetMember(member)[0];
        RangeAttribute ra = (RangeAttribute)mi.GetCustomAttributes(typeof(RangeAttribute), false)[0];
        return ((int)ra.Minimum).ToString();
    }
    catch (Exception)
    {
        return "0";
    }
}

The method signature requires that we pass in a Type and the name of the member we want information on. Using that information we get the MemberInfo object setup, extract the RangeAttribute and return the minimum value as it was entered. I also implemented a RangeMaximum (not listed here) which follows much of the same pattern, but returns 100 as the default if there is an exception.

Now, we're able to call this method and extract the property's minimum value from any view in our project where we import the namespace simply by calling Html.RangeMinimum(type, memberName).

The Custom User Control - Range.ascx

As part of the MVC folder convention, we have to create a couple of directories so the Framework understands our request when we ask for a custom control to be created. In the Views folder, Create a Shared folder if you don't already have one. Inside that, create a EditorTemplates folder and finally add an MVC 2 View User Control called Range.ascx to EditorTemplates. You should see this in your solution explorer:

Image

We have three things to do here:
To import the namespace add <%@ Import Namespace="jQueryMVC.Areas.Slider.Helpers" %> to the top of your control source (obviously updating the namespace as required for your project).

The page markup will consist of a SPAN, a hidden INPUT and a DIV.
<span id="display-amount-<%: Html.ViewData.ModelMetadata.PropertyName %>"></span>
<input type="hidden" id="<%: Html.ViewData.ModelMetadata.PropertyName %>" name="<%: Html.ViewData.ModelMetadata.PropertyName %>" />
<div style="padding:5px;display:inline; width:30%;"><div  id="slider-<%: Html.ViewData.ModelMetadata.PropertyName %>"></div></div>

You'll notice that I'm using Html.ViewData.ModelMetadata.PropertyName to generate the IDs. When dealing with a control like this, the model is the property that we rendering. We can also use Html.ViewData.ModelMetadata.ContainerType to get the class that the property belongs to. I use that in the script below, but will leave out the long form for illustration purposes and use "..." to represent Html.ViewData.ModelMetadata.
$(function () {
    // get our values from the class's RangeAttribute
    var $min = <%: Html.RangeMinimum(...ContainerType, ...PropertyName)%>;
    var $max = <%: Html.RangeMaximum(...ContainerType, ...PropertyName)%>;

// setup our slider $('#slider-<%: ...PropertyName %>').slider({ min: $min, max: $max, value: ($min + $max) / 2, slide: function(event, ui) { $("#display-amount-<%: ...PropertyName %>").html(ui.value); $("#<%: ...PropertyName %>").val(ui.value); } });

// init our display and hidden input $("#display-amount-<%: ...PropertyName %>").html($('#slider-<%: ...PropertyName %>').slider("value")) $("#<%: ...PropertyName %>").val($('#slider-<%: ...PropertyName %>').slider("value")); });

Our script makes use of the RangeMinimum and RangeMaximum HtmlHelper extension methods we created earlier. With the data stored in some local vars, we create the slider in our placeholder DIV, set a default (the halfway point) and create an anonymous function to keep the client-side input and display in sync with the slider. Finally, we initialize the values of our display SPAN and set the hidden INPUT to the starting value.

Putting it all together (and adding back in the full syntax for type and property) looks like this:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<%@ Import Namespace="jQueryMVC.Areas.Slider.Helpers" %>

<span id="display-amount-<%: Html.ViewData.ModelMetadata.PropertyName %>"></span> <input type="hidden" id="<%: Html.ViewData.ModelMetadata.PropertyName %>" name="<%: Html.ViewData.ModelMetadata.PropertyName %>" /> <div style="padding:5px;display:inline; width:30%;"><div id="slider-<%: Html.ViewData.ModelMetadata.PropertyName %>"></div></div>

<script type="text/javascript" language="javascript"> $(function () { var $min = <%: Html.RangeMinimum(Html.ViewData.ModelMetadata.ContainerType, Html.ViewData.ModelMetadata.PropertyName)%>; var $max = <%: Html.RangeMaximum(Html.ViewData.ModelMetadata.ContainerType, Html.ViewData.ModelMetadata.PropertyName)%>; $('#slider-<%: Html.ViewData.ModelMetadata.PropertyName %>').slider({ min: $min, max: $max, value: ($min + $max) / 2, slide: function(event, ui) { $("#display-amount-<%: Html.ViewData.ModelMetadata.PropertyName %>").html(ui.value); $("#<%: Html.ViewData.ModelMetadata.PropertyName %>").val(ui.value); } }); $("#display-amount-<%: Html.ViewData.ModelMetadata.PropertyName %>") .html($('#slider-<%: Html.ViewData.ModelMetadata.PropertyName %>').slider("value")) $("#<%: Html.ViewData.ModelMetadata.PropertyName %>") .val($('#slider-<%: Html.ViewData.ModelMetadata.PropertyName %>').slider("value")); }); </script>

The Updated Class

We have one last task to attend to so that the framework knows that we would like this control to be used when the UI is rendered. To do this, we need to deocorate the property with a UIHintAttribute and let it know about our Range control.
public class Course
{
    public string Title { get; set; }
    public string Description { get; set; }

[Range(8, 75)] [UIHint("Range")] public int StudentCount { get; set; } }

Again, in a nutshell, we:
Try Me!

Wrapping Up

The slider is a great UI enhancement, and we've seen a couple of different ways to put it to use. When we use the benefits of ASP.NET and the MVC Framework, we can also make using the slider dead simple and highly reusable.

About the Author

James Chambers is a Senior Software Developer in the prairies of Canada where he is regarded among locals as an expert in spotting polar bears in snow drifts. His development passions are fueled by new tech, new tools and mentoring others. Follow his coding adventures at Mister James.