Advertisements
Home > javascript, jQuery > Time span sliders in jQuery

Time span sliders in jQuery

I’ve been working on prototyping a new application that needed a way for users to schedule things in spans of time on multiple days. It should be quick and easy for people to pick arbitrary time spans on each day of the week. The main use case is to set a day time block, during business hours for example, or a block that excludes business hours. Someone might schedule the time span from 8am to 5pm, or they might schedule it to end at 8am and then start again at 5pm. I looked around but didn’t find any examples of doing anything like this that I liked.

I settled on a slider that you can invert. So you can set the range from 8am to 5pm, then click on a check box that flips the slider to mean everything except for 8am to 5pm. It needs to be visually obvious what this is doing, so the slider and text needs to reflect what is happening. I think the result is fairly easy to use and very workable.  The layout looks something like this:

The other thing I wanted was to be able to drop the whole thing in a page easily based on code in one place. I anticipate it will be used in more than one location within the application. So I wanted to construct it as a jQueryUI widget that basically wraps the standard slider widget. I didn’t try to make it portable beyond this app. There are a few things I’ve done here that you wouldn’t want to do if you were making a normal jQueryUI plugin.  For example, it carries the HTML for laying out the text and sliders within the widget.  I think its illustrative for a prototype, but not elegant.

Posts on doing multiple sliders or time based sliders are not easy to find. The best one I found was from Marc Neuwirth. That post saved me some time in figuring out how to deal with the times on the slider. There is also an improved slider on the Filament Group’s site, but it doesn’t really answer the basic needs I had.  The fact that I had a hard time finding examples from other people who’d already done the same thing is what prompted this post.

For a time slider, we need to convert the times to integers so we can use them for slider values. To convert the time to an integer, we split the time into hours and minutes, multiply the hours times 60, and add back the minutes. We do the reverse to turn the integer into a formatted time for display. In the code below these operations are done in two functions, named formatTime and getTime. These are single use functions I don’t have a need for elsewhere, so I’m carrying them inside the narrow scope. If I ended up having a similar need somewhere else I’d refactor that out to make it usable elsewhere.

Since I’m making a jQueryUI widget (sort of), I start with the basic self executing function and the widget factory method.

(function($){

    $.widget("ui.timeslider", {
    });

})(jQuery);

To that, I need to add the options object, which will allow us to set our options for the widget and also double as the options we’ll pass to the individual sliders. That last bit gets a little sticky, because we’ll have a changing scope as we deal with events within seven different slider (sub)widgets. Usually I wouldn’t put the whole callback function in the options definition, since its harder to read and maintain, but its easier to make it portable to each of the sliders this way.

options: {
    animate: false,
    days: ["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],
    distance: 0,
    max: 1440,
    min: 0,
    orientation: "horizontal",
    range: true,
    slide: function(event,ui){
        function formatTime(time){
            var hours = parseInt(time / 60 % 24);
            var minutes = parseInt(time % 60);
            minutes = minutes + "";
            if (minutes.length == 1) {
                minutes = "0" + minutes;
            }
            return hours + ":" + minutes;
        }
        var startTime = formatTime(ui.values[0]);
        var endTime = formatTime(ui.values[1]);

        var selector = "#"+this.id;
        $(selector + "-time1").val(formatTime(ui.values[0]));
        $(selector + "-time2").val(formatTime(ui.values[1]));
    },
    step: 5,
    value: 0,
    values: [360, 1080]
},

Most of these options are the standard slider options. The only addition is the days array, which we’ll use to add the individual days to our results. Putting it in the options this way lets us pass in a different set of days if we wanted to (maybe Monday through Friday are more appropriate in some places). The slide callback is basically a typical callback for the slider widget. The id based selector will make more sense in a bit.

The rest of the widget is all in two methods: _create and _sliderhtml. _sliderhtml just holds our html to build the containers for the individual sliders. The underscore at the front of the method names are how we tell the jQueryUI factory that they are private.

_sliderhtml: function(day){
    var displayday = day.charAt(0).toUpperCase() + day.substr(1);
    var html = ""+ 
        '<div class="fieldrow spacer">' +
            '<div>' +
                '<label for="'+day+'-time" class="inline">'+displayday+':</label>&nbsp;' +
                '<span id="'+day+'-midnight1" style="display:none">midnight to</span>&nbsp;'+
                '<input type="text" name="'+day+'-time1" id="'+day+'-time1" value="6:00" class="blended"> - '+
                '<input type="text" name="'+day+'-time2" id="'+day+'-time2" value="18:00" class="blended">&nbsp; '+
                '<span id="'+day+'-midnight2" style="display:none">to midnight</span>&nbsp;&nbsp;&nbsp;'+
                '<span>Except <input type="checkbox" name="'+day+'-except" id="'+day+'-except"></span>' +
            '</div>' +
            '<div id="'+day+'"></div>' +
        '</div>';
    return html;
}

That’s not pretty, and the wrapping displayed here makes it look even worse. This is special use widget, intended for one application. I wouldn’t build more general use widgets with the HTML like this, because you have to edit the widget code to change the layout, but its fine for my limited purpose here. This is the HTML for one slider. We’ll pass it the day from the days array in the options and use the day name for our field names and element IDs. We change the first letter of the day’s name to upper case to make the label.

_create is the standard method that will be used by the jQueryUI to construct our widget. It looks like this:

_create: function(){

    var self = this,
        o = this.options,
        dayslength = o.days.length;
    o.timeslider = this;

    function getTime(time){
        var times = time.split(':');
        time = (times[0] * 60) + parseInt(times[1]);
        return time;
    }

    for(var i=0; i < dayslength; i++){
        self.element.append(self._sliderhtml(o.days[i]));
        $( "#" + o.days[i] ).slider(o);
        $( "#" + o.days[i] + "-time1" ).bind("change",function(){
            var slider = "#" + this.id.substr(0,this.id.length - 6);
            var newval = getTime($("#"+this.id).val());
            if(!isNaN(newval)) $(slider).slider("values" , 0 , newval);
        });
        $( "#" + o.days[i] + "-time2" ).bind("change",function(){
            var slider = "#" + this.id.substr(0,this.id.length - 6);
            var newval = getTime($("#"+this.id).val());
            if(!isNaN(newval)) $(slider).slider("values" , 1 , newval);
        });
        $( "#" + o.days[i] + "-except" ).bind("change",function(){
            var excl = "#"+this.id;
            var sliderlength = excl.length - 7;
            var slider = excl.substr(0,sliderlength);
            var exclude = $(excl).is(":checked");
            if(exclude){
                $(slider).addClass("ui-widget-header");
                $(slider).removeClass("ui-widget-content");
                $(slider + " div").addClass("ui-widget-content");
                $(slider + " div").removeClass("ui-widget-header");
                $(slider + "-midnight1").css("display","inline");
                $(slider + "-midnight2").css("display","inline");
            } else {
                $(slider).addClass("ui-widget-content");
                $(slider).removeClass("ui-widget-header");
                $(slider + " div").addClass("ui-widget-header");
                $(slider + " div").removeClass("ui-widget-content");
                $(slider + "-midnight1").css("display","none");
                $(slider + "-midnight2").css("display","none");
            }
        });
    }
},

The interesting part, and basically the whole reason for the widget to exist, is in the for loop in this method. self.element is the element from the selector in our page. So we might create the widget like this:
$("#scheduler").timeslider();
In that case the DOM element with “scheduler” as the ID will be self.element within the _create method. We iterate through our options.days array and append the html from _sliderhtml for each day in the array. Then we put a slider widget in each of those blocks of HTML (line 16 above), passing it the same options array we’re using. It has the extra days array, but the slider widget will ignore that. The rest of the loop is adding event bindings to the three input elements embedded in our HTML.

The first two input elements hold the times. This makes grabbing the values extremely easy, because we have an input element with an ID. We put the values in these fields in the slide callback in the options (line 23 and 24 in the second code block in this post). Here we’re binding an onchange to these elements. That allows a user to click on the times and type in a new value, which will move the slider to what they typed. It might be that nobody will ever do that, but I think it’s a nice feature.

The third event we’re binding is a change event to our checkbox. The string manipulation on lines 29 and 30 is identifying the various elements that make up our layout. On line 31 we see if the checkbox is checked or not, and then depending on that we set styling on the slider.

As a last step, I added another checkbox at the top to disable the whole thing, but that’s not actually in the widget. When you click the checkbox it does this:

function(){
    if($("#turnitoff").is(":checked")){
        $("#scheduler").timeslider("disable");
    } else {
        $("#scheduler").timeslider("enable");
    }
}

This highlights the benefits of using the jQueryUI widget factory for this exercise, if that wasn’t clear already. By using the widget like this, we get the disable and enable methods, and all the other standard widget methods built in.

This is prototype, and I’m not sure yet if it will make it into the production application. If it does (and I remember) I’ll update this to show it used in context. If it gets into production it will likely need some refinement. For example, the checkbox might be better as a button and “Except” is not the right label, and there are a few other things I’d tweak. In the meantime, I hope this is useful for someone.

Advertisements
Categories: javascript, jQuery
  1. JV
    June 11, 2011 at 11:50 pm

    Great tool !! exactly what I wanted. Being a newbie to JQuery , can you help me out with the call back functions and how to get the values from these sliders to update a DB table ?

    • David
      June 16, 2011 at 1:23 pm

      Since I put the values from the sliders into the input fields (lines 23 and 24 of the second block of code in the post) on each slide event, you don’t have to get the values from the slider when you are submitting. It’s just a matter of submitting the form fields as you would normally submit a form. I generally use the jQuery form plugin for that.

  2. August 22, 2012 at 2:00 am

    Hi David,
    I am trying to replicate the widget you created but I just can’t. Do you have a working example of it? I would really appreciate your help.
    Thanks!

    • David
      August 22, 2012 at 7:39 am

      This is in use on http://nodeping.com. You have to sign up to see it, but there’s a free trial so it won’t cost you anything to look at it there. The widget is used to set “Notification Windows” under “Account Settings.”

      • August 22, 2012 at 7:58 am

        Tanks for the quick reply. I managed to set it also. I forgot to include the jquery ui library when I first tried to use your widget. 😛

  3. August 22, 2012 at 11:58 pm

    Hi David,
    It’s me again. As I mentioned, I managed to put it all together, and I even took out the checkbox inputs, as I didn’t need them. My problem is that I want to be able to add a new time interval slider to a day. I made some minor modifications like creating a button next to each day slider, but I cannot go any further, as you can see here: http://jsfiddle.net/inadcod/GyanJ/. If you have some time and would like to take a look, I would highly appreciate your help.
    Thanks.

  4. October 16, 2012 at 1:15 am

    Hi David, fantastic walkthrough – has saved me a lot of time. Just wondering if you could shed some light on how to set the variables from a feed after saving. Currently I can’t get the slider to pull the values in per day.

    • David
      October 16, 2012 at 3:53 pm

      Each of the sliders has two input fields (day+’-time1′ and day+’-time2′), and these fields are bound to a change event that sets the sliders. So, to set the times just set the value in those two input fields and then trigger the change event for that element. Something like this (assuming time1 and time2 are variables holding your time values from wherever you are getting them):


      $("#" + day + "-time1").val(time1).trigger('change');
      $("#" + day + "-time2").val(time2).trigger('change');

  5. Rol
    February 1, 2013 at 2:09 pm

    Hi David! You can use this script in multiple instances in 1 form? ex.- #scheduler1 and #scheduler2

    Thanks! Best Regards!

    • David
      February 1, 2013 at 2:34 pm

      I haven’t, but you could. You would have to change things (particularly the html pieces) slightly to make sure that all of the element IDs are unique.

      • Rol
        February 1, 2013 at 3:06 pm

        Thanks for the quick reply! im really not very pro in jquery, i’ve put this code in a php and it worked, but i’ve been trying for hours to put 2 of these in 1 form, but doesnt work…thats why i came here for any kind of advice, if you can give me 1 hint to start, it would be awesome!

        Thanks for your time!

      • David
        February 4, 2013 at 3:05 pm

        Sorry, without looking at what you’re doing the only advice I can really give is to make sure all of the id’s are unique. Unfortunately I don’t really have time to look over your code.

        The other approach is to put these in dialogs rather than embedded in one form, and separate your flow into separate steps so each widget appears by itself. Then you dodge the problem of them interfering with each other.

  6. Rol
    February 4, 2013 at 3:36 pm

    Thanks a lot for your answer, maybe i will use the code in a dialog….thanks again for your time!

  7. Rol
    February 4, 2013 at 6:40 pm
  1. May 16, 2012 at 11:06 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: