/* WARNING: This is not (yet) a complete implementation, the following
 * features are missing:
 *  - Binding on events does not work as expected.
 *  - These events are not implemented at all: create, start, change and stop.
 *  - The value and values method do not yet return the calculated values.
 * All other features should work as expected:
 *  - All options the jQuery slider accepts with the exception of step.
 *  - The additional non-linear specific options: mid, curve and granularity.
 *  - The slide event when given as an option.
 *  - All methods the jQuery slider has with the exception of value and values.
 *  - Theming works the same as with the standard slider.
 */

/* Containing the scope is good practice in jQuery code, 
 * this has the added bennefit of being able to use $ even when the user of
 * the plugin has disabled this. */
(function($, undefined) {
  /* jQuery-ui has this nice function for defining widget plugins */
  $.widget( "ui.nlslider", $.ui.slider, {
    /* We largely inherit the ui.slider functionality, but the extra options
     * and default values are here: */
    options: {
      /* If defined, the value of the mid option will be at the center of the
       * slider. A nice value would be the median of your dataset, that way the
       * slider slides about linear with the percentiles (for most datasets) */
      mid: null,
      /* The raw value from the slider is used to find a value between min and
       * max, by taking the position to the n-th power, n is calculated using
       * the mid option. If the mid option is not given, the the curve option
       * is used to get a specific value for n directly. */
      curve: Math.E,
      /* The granularity specifies the number of raw positions the jQuery
       * slider returns, the desired value depends mostly on the size of the
       * widget */
      granularity: 100,
    },
    /* The _create function is the entry point of the widget creation, we use
     * it to mangle the normal jQuery-ui slider, so this is rather long */
    _create: function() {
      /* First place some variables in the scope */
      var self = this, o = this.options, out_min = o.min,
          range = o.max - o.min, slide_callback = o.slide;
      /* Calculate to curve value if the mid option is used, or fall back to
       * the curve option */
      var curve = (o.mid != null && (o.mid >= o.min || o.mid <= o.max)) ?
                  (Math.log((o.mid - o.min) / range) / Math.log(0.5)) :
                  o.curve;
      /* The calculations from/to the value/position */
      var calculate_value = function( position ) {
        return out_min + Math.pow(position / o.granularity, curve) * range;
      };
      var calculate_position = function( value ) {
        return Math.pow((value - out_min) / range, 1 / curve) * o.granularity;
      };
      /* Update the options */
      var newOptions = {
        /* the step option does not make sense with this slider, the min and
         * max values are used for the granularity, and the other options are
         * left as-is */
        step: undefined,
        min: 0,
        max: o.granularity,
        /* The new callback, here we calculate the new values, and call the
         * original callback */
        slide: function(event, ui) {
          var newVal = calculate_value(ui.value);
          var newUi = {handle: ui.handle, value: newVal};
          /* ui.values is available if we are a range slider */
          if (ui.values) {
            newUi.values = [calculate_value(ui.values[0]),
                             calculate_value(ui.values[1])];
          };
          /* We are done calcutating the new values, the user might still want
           * to cancel the slide, so we pass their return value along to the
           * slider code */
          if (slide_callback) return slide_callback.call(this, event, newUi);
        }
      };
      /* Calculate the original positions */
      if(o.value) newOptions.value = calculate_position(o.value);
      if(o.values) newOptions.values = [calculate_position(o.values[0]),
                                        calculate_position(o.values[1])];
      $.extend(this.options, newOptions);
      /* We have updated the options, so we can go ahead and instantiate the
       * slider using the jQuery UI implementation */
      return $.ui.slider.prototype._create.call(self);
    }
  });
})(jQuery);
© 2011 Môshe van der Sterre