Google analytics script

Latest jQuery CDN with code tiggling.

Friday, 12 August 2011

jQuery UI slider extension that enhances range capabilities

I'm going to provide you with the code that makes it possible to set bounds to range slider handles (minimum and maximum) and some more sugar along with it.

I needed to create a range slider that is a bit smarter than the one provided by jQuery UI library that only allows to set minimum and maximum values for the whole slider, but I needed to set a few things more. I had to provide a range slider that would allow users to select time range between midnight and midday the next day (36 hours all together). These were my requirements:

  • Lower handle can only move to midnight the next day (so it can move between 00:00 and 1d 00:00) - this simply means that time range must start today
  • Selected range must be at least 1 hour wide
  • Selected range can't be wider than 24 hours all together
These were my requirements and although I like jQuery UI plugins/widgets and even though it comes with a slider it doesn't work as expected. I could of course put it all in my slide handler, but tha wouldn't be reusable and it would also not allow me to do some additional things I implemented along. Want to know which ones?

Parsing jQuery UI code

The first thing was to delve deep into jQuery UI code and try to decipher its inner workings. When that was done I also had to come up with a solution to just change existing plugin code instead of providing my own plugin based on original one which would 99% copy it and just add some additions that I needed. But I accomplished both of these tasks so instead of coming up with my own range slider plugin I've come up with an extension to existing range slider.

Extension raw code

This extension code will only extend slider if it's loaded so it's somehow unobtrusive since it doesn't do anything when your page scripts don't include jQuery UI slider plugin.

   1:  /*
   2:   * jQuery UI Slider Range extension 1.1 (25 Oct 2011)
   3:   *
   4:   * This extension is based code provided by
   5:   * jQuery UI Slider version 1.8.13
   6:   * it may happen tht it won't work with newer versions
   7:   *
   8:   * Copyright (c) 2011 Robert Koritnik
   9:   * Licensed under the terms of the MIT and GPL-2.0 license
  10:   * http://www.opensource.org/licenses/mit-license.php
  11:   * http://www.opensource.org/licenses/GPL-2.0
  12:   *
  13:   */
  14:   
  15:  (function($) {
  16:      if ($.ui.slider)
  17:      {
  18:          // add minimum range length option
  19:          $.extend($.ui.slider.prototype.options, {
  20:              minRangeSize: 0,
  21:              maxRangeSize: 100,
  22:              autoShift: false,
  23:              lowMax: 100,
  24:              topMin: 0
  25:          });
  26:   
  27:          $.extend($.ui.slider.prototype, {
  28:              _slide: function(event, index, newVal) {
  29:                  var otherVal,
  30:                  newValues,
  31:                  allowed,
  32:                  factor;
  33:   
  34:                  if (this.options.values && this.options.values.length)
  35:                  {
  36:                      otherVal = this.values(index ? 0 : 1);
  37:                      factor = index === 0 ? 1 : -1;
  38:   
  39:                      if (this.options.values.length === 2 && this.options.range === true)
  40:                      {
  41:                          // lower bound max
  42:                          if (index === 0 && newVal > this.options.lowMax)
  43:                          {
  44:                              newVal = this.options.lowMax;
  45:                          }
  46:   
  47:                          // upper bound min
  48:                          if (index === 1 && newVal < this.options.topMin)
  49:                          {
  50:                              newVal = this.options.topMin;
  51:                          }
  52:   
  53:                          // minimum range requirements
  54:                          if ((otherVal - newVal) * factor < this.options.minRangeSize)
  55:                          {
  56:                              if (this.options.autoShift === true)
  57:                              {
  58:                                  otherVal = newVal + this.options.minRangeSize * factor;
  59:                              }
  60:                              else
  61:                              {
  62:                                  newVal = otherVal - this.options.minRangeSize * factor;
  63:                              }
  64:                          }
  65:   
  66:                          // maximum range requirements
  67:                          if ((otherVal - newVal) * factor > this.options.maxRangeSize)
  68:                          {
  69:                              if (this.options.autoShift === true)
  70:                              {
  71:                                  otherVal = newVal + this.options.maxRangeSize * factor;
  72:                              }
  73:                              else
  74:                              {
  75:                                  newVal = otherVal - this.options.maxRangeSize * factor;
  76:                              }
  77:                          }
  78:                      }
  79:   
  80:                      if (newVal !== this.values(index))
  81:                      {
  82:                          newValues = this.values();
  83:                          newValues[index] = newVal;
  84:                          newValues[index ? 0 : 1] = otherVal;
  85:                          
  86:                          // A slide can be canceled by returning false from the slide callback
  87:                          allowed = this._trigger("slide", event, {
  88:                              handle: this.handles[index],
  89:                              value: newVal,
  90:                              values: newValues
  91:                          });
  92:                          if (allowed !== false)
  93:                          {
  94:                              this.values(index, newVal, true);
  95:                              this.values(index ? 0 : 1, otherVal, true);
  96:                          }
  97:                      }
  98:                  }
  99:                  else
 100:                  {
 101:                      if (newVal !== this.value())
 102:                      {
 103:                          // A slide can be canceled by returning false from the slide callback
 104:                          allowed = this._trigger("slide", event, {
 105:                              handle: this.handles[index],
 106:                              value: newVal
 107:                          });
 108:                          if (allowed !== false)
 109:                          {
 110:                              this.value(newVal);
 111:                          }
 112:                      }
 113:                  }
 114:              }
 115:          });
 116:      }
 117:  })(jQuery);

Additional slider configuration options

There are a few new slider options that this range slider uses to accomplish upper requirements:

  • minRangeSize and maxRangeSize - provide these two if you want your range slider to have range width limitations; one sets minimum range width the other maximum
  • autoShift - this is a special option that sets range slider behaviour when any of your handles reaches maximum range width; if you set this option to true dragging the handle beyond may range width will automatically drag the other handle along; if you set it to false handle will simply stop moving beyond maximum range width
  • lowMax - sets the low handle upper bound value; you can set your lower range bound handle to not move over a certain value (in my case it wasn't allowed to move beyond midnight next day and this setting made it possible)
  • topMin - similar to lowMax except that it sets minimum value for the upper range bound handle; I didn't have to use this one, but since I've set one bound it made sense to also provide limitation for the other.

Additional questions?

If you happen to use this extension and have additional questions, do let me know. Hope this extension will help many of you, since I've seen many have requested for bounds settings for range slider yet core jQuery UI team didn't support this functionality.

18 comments:

  1. This is fantastic. I have made a small adjustment... and added the following to line 54 to add autoShift on the minimum too. Thanks for sharing.

    if (this.options.autoShift === true)
    {
    otherVal = newVal + this.options.minRangeSize * factor;
    }
    else
    {
    newVal = otherVal - this.options.minRangeSize * factor;
    }

    ReplyDelete
  2. @Aaron Vanderzwan: That's actually something that should be included in the first place.
    When auto shifting is enabled for maximum range it should be for minimum as well.

    Thanks for sharing. I'll put it in.

    ReplyDelete
  3. Thanks for putting this together. It handily resolved a problem.

    I put your code into it's own .js file and included it along with the jquery ui code. I set the options like so within the .js file...

    $.extend($.ui.slider.prototype.options, {
    minRangeSize : 1,
    maxRangeSize : 48,
    autoShift : false,
    lowMax : 23,
    topMin : -23
    });

    But this makes the extension that you wrote specific to the slider. I can't reuse the .js file with another slider that has a different minRangeSize for example.

    How do I set the options from within the .jsp file where the slider instance exists?

    I tried something like this...

    $(function() {
    $("#slider-range").slider({
    range: true,
    min: -24,
    max: 24,
    values: [-10, 10],
    slide: function(event, ui) {
    //
    });

    // set the slider range extension options
    $("#slider-range").slider.prototype {
    minRangeSize : 1,
    maxRangeSize : 48,
    autoShift : false,
    lowMax : 23,
    topMin : -23
    });

    The above doesn't work though.

    I can imagine why this doesn't work but I don't have a strong enough understanding of jquery extensions to get my head around it.

    Thanks once again for putting this together and as well for any insight you can offer about setting the options.

    Cheers,

    Andrew

    ReplyDelete
  4. @Andrew: Just add my extension script after jquery UI. Then just use these additional options as if they were part of the original slider plugin when creating a new instance (and not changing prototype as you tried doing). Like so:


    $("#slider-range").slider({
        range: true,
        min: -24,
        max: 24,
        values: [-10, 10],
        // additional ones
        minRangeSize : 1,
        lowMax : 23,
        topMin : -23,
        // functions
        slide: function(event, ui) {
            // functionality
        }
    });


    And don't use defaults and lowMax and topMin because they're already constrained by the minimum range width.

    But... Try with and without if it works in both cases because you're using negative values, so it may be tricky actually. I admit I haven't tested for such values.

    ReplyDelete
  5. Hi Robert,

    Thanks, that was how I thought I should be setting the options, but when I tried it my syntax must have been wrong.

    My colleague and I played around with your extension's option values for a little bit and he remarked that the slider is kind of game-like, that is to say that it could be useful for expressing and gathering information in a playful way.

    By the way, the slider and extension worked fine with negative numbers. Also I already had the file with the extension after jquery-ui.js, but just to be sure I also placed it after the jquery-ui.css theme. Didn't test if that made any difference, just throwing it out for future readers.

    Thanks again.

    Andrew

    ReplyDelete
  6. @Andrew: I'm not sure I understand your colleague's remarks but I take your comment as extension's now working as expected. Thanks for letting me know that it works well with negatives so I don't have to test it myself. :)

    ReplyDelete
  7. Great! Just what i was looking for. Thank you for sharing.
    Cheers from Italy!

    ReplyDelete
  8. I am giving my first steps with jQuery and I used a slider to set a price range. The problem is that I put two different pictures on each end and do not see how can i configure
    the css background properties...

    Thanks,

    Xa0z

    ReplyDelete
  9. Xa0z: I'm also using the same approach in my slider use. but I have to tell you that styling the slider is not part of my plugin. You can easily set CSS styles as you need. I suggest you check CSS classes of the generated slider elements by using ie. Firebug in FF...

    I've set these styles:


    .ui-slider

    .ui-slider .ui-slider-range

    .ui-slider .ui-slider-handle

    .ui-slider .ui-slider-handle.ui-state-focus

    .ui-slider .ui-slider-handle + .ui-slider-handle

    .ui-slider .ui-slider-handle + .ui-slider-handle.ui-state-focus


    Particularly the last couple of CSS selectors set a different handle image for the second slider handle.

    ReplyDelete
  10. Robert,
    I noted that on Stackoverflow a while back you were looking for a slider that supported multiple ranges. As you said then JQueryUI Slider can do multiple handles but not multiple ranges.
    Any luck finding such a extension/control?

    Thanks,
    John Kelleher

    ReplyDelete
    Replies
    1. Hi John,

      no not really. I needed a range exclude range selection so everything else except range you select. I would likely change existing jQuery UI slider to support this, but eventually I didn't need it.

      Delete
  11. usually i never comment on this things, but ey you deserve it. great job, so glad that you coded this

    ReplyDelete
  12. Great extension. One comment though. I wish you would package this with a demo page explaining the new options. Thanks again.

    ReplyDelete
    Replies
    1. I should be putting this on GitHub anyway. Will have to find the time some day.

      Delete
  13. Can we have same options for Date Range Slider as well?

    ReplyDelete
    Replies
    1. This slider doesn't imply any particular data type. You can apply it to dates if you wanted. I've used it with time, although it's internally working with integers.

      Delete