Monday, December 2, 2013

Dynamic Slider Step Values

The jQuery UI slider widget defaults to a step value of 1. That means that each time the slider handle is moved, the slider value is incremented or decremented by 1. We can specify a different step value, and have these increments and decrements changed accordingly. For example, we could set the step option to 5, and have an initial value of 10. Moving the slider to the right, the next value would be 15, and so on. Not all data series work this way. Think Fibonacci. Unfortunately, a fixed step is the only thing we can supply to the slider value. However, we can map the default increments to the numbers in our series.

The basic idea can seen here, where the step values map to a series value that we want displayed when the user moves the slider handle. I've taken this idea and added the dynamic step values to the slider event data. This is done by passing in the value map to the slider constructor — a new steps option. For example, using a slider to display some Fibonacci numbers.


Let's take a look at the code used to extend the slider widget in this example.

var events = [ "create", "start", "stop", "slide", "change" ];

First, we have the events variable. This variable is declared outside of the widget extension itself since it's static, and we only need to allocate it once. These are the events that we want to extend by adding additional data. Namely, the stepValue property which is the value mapped from the steps option.

_getCreateEventData: function() {
    return { value: this.value() };
}

Next is the _getCreateEventData() method. This is a standard jQuery UI extension point for all widgets. It's called by the widget constructor in order to get any data to be provided in the create event. Here, we're passing the value property to the event data, this way, we have the initial value in any create events triggered. Every other event we're extending already has a value property, so this code just brings the create event in line with the rest.


_init: function() {var steps = this.options.steps;
    if ( $.isArray( steps ) ) {
        this.option( "max", steps.length - 1 );    
    }
}

Next is the _init() method. Here we want to set the max option of the slider. But, we only do so if the steps option is an array. This is our new option, used to map series values to the actual slider step value. The max is based on the length of the passed-in array. Note that we're using the _init() method and not the _create() method. The reason for this is that the slider constructor expects a max option to be set. Setting it after the fact doesn't work so well, and since _init() is called before _create(), this approach works best for our extension.

_trigger: function( name, e, ui ) {
            
    var steps = this.options.steps;

    if ( !$.isArray( steps ) ) {
        return this._superApply( arguments );
    }
            
    if ( $.inArray( name, events ) >= 0 ) {
                
        return this._superApply([
            name,
            e,
            $.extend( ui, {
                stepValue: steps[ ui.value ]
            })
        ]);

    }
           
    return this._superApply( arguments );
            
}

The last method to override is the _trigger() method. This method is actually inherited by the slider from the base Widget class. However, when the slider widget calls it to trigger slider events, we want some custom behavior executed. First, we get the steps option and store it in the steps array. If the steps option isn't an array, we just return the default _trigger() behavior using the _superApply() method. Next we check if the triggered event is one that we're interested in extending. We do this by checking the event name is in the events array we set up earlier. If so, we call the original _trigger() method with a customized ui object — it now has a stepValue property. This value is mapped from the current slider value to the steps array.

$("#slider").slider({
    range: "min",
    steps: [ 1, 2, 3, 5, 8, 13 ],
    change: function( e, ui ) {
        $( "#amount" ).val( ui.stepValue );
    },
    create: function( e, ui ) {
        $( "#amount" ).val( ui.stepValue );
    }
});

This is how the new steps option is used — we're passing in some Fibonacci numbers. Also note the use of the new ui.stepValue property in the slider event handlers. Another benefit to extending the widget in this manor is that the original value is still available to the outside world through the value() method. In other words, we haven't disabled any of the original slider behavior by implementing this extension.