PageSourceSearch

http://www.blogosphera.net/wp-content/themes/education-way/assets/js/jquery.nstSlider.js?ver=20151215

js blogosphera.net collected 2026-10-08 11:00:50 UTC 83,664 bytes, 2,016 lines download raw bytes

1/*! Nestoria Slider - v1.0.13 - 2015-07-16
2* http://lokku.github.io/jquery-nstslider/
3* Copyright (c) 2015 Lokku Ltd.; Licensed MIT */
4(function($) {
5    /* 
6     * These are used for user interaction. This plugin assumes the user can
7     * interact with one control at a time. For this reason it's safe to keep
8     * these global.
9     */
10    var _$current_slider;
11    var _is_mousedown;
12    var _original_mousex;
13
14    // both for keyboard and mouse interaction
15    var _is_left_grip;
16
17    // for keyboard interaction only
18    var _before_keydown_value;
19    var _before_keydown_pixel;
20    var _before_keyup_value;
21    var _before_keyup_pixel;
22
23    // a fixed configuration for the single bar slider, used to decide where to
24    // place the naked bar.
25    var _naked_bar_deltas; // see populateNakedBarDeltas
26
27    var _methods = {
28         /*
29          * This method must be called once during initialization.
30          * It sets the behaviour of the naked bar in case of one handle.
31          */
32         'setNakedBarDelta': function (position, handleWidth) {
33             if (position === "stickToSides") {
34                _naked_bar_deltas = {
35                    toEndWidth: handleWidth,
36                    toBeginLeft: 0,
37                    toBeginWidth: handleWidth
38                };
39             }
40             else if (position === "middle") {
41                // Position naked end of the bar at the middle value.
42                _naked_bar_deltas = {
43                    toEndWidth: handleWidth/2,
44                    toBeginLeft: handleWidth/2,
45                    toBeginWidth: handleWidth/2
46                };
47             }
48             else {
49                throw new Error('unknown position of setNakedBarDelta: ' + position);
50             }
51         },
52         'getSliderValuesAtPositionPx' : function (leftPx, rightPx) {
53              var $this = this,
54                  leftPxInValue, rightPxInValue,
55                  pixel_to_value_mapping_func = $this.data('pixel_to_value_mapping');
56
57              if (typeof pixel_to_value_mapping_func !== 'undefined') {
58                  leftPxInValue = pixel_to_value_mapping_func(leftPx);
59                  rightPxInValue = pixel_to_value_mapping_func(rightPx);
60              }
61              else {
62                  var w = _methods.getSliderWidthPx.call($this) - $this.data('left_grip_width');
63                  leftPxInValue = _methods.inverse_rangemap_0_to_n.call($this, leftPx, w);
64                  rightPxInValue = _methods.inverse_rangemap_0_to_n.call($this, rightPx, w);
65              }
66
67              return [leftPxInValue, rightPxInValue];
68         },
69         /*
70          *  Move slider grips to the specified position. This method is
71          *  designed to run within the user interaction lifecycle. Only call
72          *  this method if the user has interacted with the sliders
73          *  actually...
74          *
75          *  First the desired positions are validated. If values are ok, the
76          *  move is performed, otherwise it's just ignored because weird
77          *  values have been passed.
78          */
79         'validateAndMoveGripsToPx' : function (nextLeftGripPositionPx, nextRightGripPositionPx) {
80             var $this = this;
81
82             var draggableAreaLengthPx = _methods.getSliderWidthPx.call($this) - $this.data('left_grip_width');
83
84             //
85             // Validate & Move
86             //
87             if (nextRightGripPositionPx <= draggableAreaLengthPx && 
88                 nextLeftGripPositionPx >= 0 &&
89                 nextLeftGripPositionPx <= draggableAreaLengthPx && 
90                 (!$this.data('has_right_grip') || nextLeftGripPositionPx <= nextRightGripPositionPx) ) {
91
92                 var prevMin = $this.data('cur_min'),                        
93                     prevMax = $this.data('cur_max');       
94
95                 // note: also stores new cur_min, cur_max
96                 _methods.set_position_from_px.call($this, nextLeftGripPositionPx, nextRightGripPositionPx);
97
98                 // set the style of the grips according to the highlighted range
99                 _methods.refresh_grips_style.call($this);
100
101                 _methods.notify_changed_implicit.call($this, 'drag_move', prevMin, prevMax);
102             }
103
104             return $this;
105         },
106         /*
107          * Update aria attributes of the slider based on the current
108          * configuration of the slider.
109          */
110         'updateAriaAttributes' : function () {
111            var $this = this,
112                settings = $this.data('settings'),
113                $leftGrip = $this.find(settings.left_grip_selector);
114
115            //
116            // double grips sliders is probably the most common case...
117            // ... also, the values to be set in the two cases are quite
118            // different.
119            //
120            if ($this.data('has_right_grip')) {
121
122                var $rightGrip = $this.find(settings.right_grip_selector);
123
124                //
125                // grips are mutually binding their max/min values when 2 grips
126                // are present. For example, we should imagine the left grip as
127                // being constrained between [ rangeMin, valueMax ]
128                //
129                $leftGrip
130                    .attr('aria-valuemin', $this.data('range_min'))
131                    .attr('aria-valuenow', methods.get_current_min_value.call($this))
132                    .attr('aria-valuemax', methods.get_current_max_value.call($this));
133
134                $rightGrip
135                    .attr('aria-valuemin', methods.get_current_min_value.call($this))
136                    .attr('aria-valuenow', methods.get_current_max_value.call($this))
137                    .attr('aria-valuemax', $this.data('range_max'));
138            }
139            else {
140                $leftGrip
141                    .attr('aria-valuemin', $this.data('range_min'))
142                    .attr('aria-valuenow', methods.get_current_min_value.call($this))
143                    .attr('aria-valuemax', $this.data('range_max'));
144            }
145
146            return $this;
147         },
148         /*
149          * Return the width in pixels of the slider bar, i.e., the maximum
150          * number of pixels the user can slide the slider over. This function
151          * should always be used internally to obtain the width of the
152          * slider in pixels!
153          */
154         'getSliderWidthPx' : function () {
155            var $this = this;
156
157            //
158            // .width() can actually return a floating point number! see
159            // jquery docs!
160            //
161            return Math.round($this.width());
162         },
163         /*
164          * Return the position of a given grip in pixel in integer format.
165          * Use this method internally if you are literally going to get the
166          * left CSS property from the provided grip.
167          *
168          * This method assumes a certain grip exists and will have the left
169          * property.
170          *
171          * This is generally safe for the left grip, because it is basically
172          * guaranteed to exist. But for the right grip you should be really
173          * using getRightGripPositionPx instead.
174          *
175          */
176         'getGripPositionPx' : function ($grip) {
177            return parseInt($grip.css('left').replace('px',''), 10);
178         },
179         /*
180          * Just the same as getGripPositionPx, but there is no need to provide
181          * the $slider.
182          */
183         'getLeftGripPositionPx' : function () {
184            var $this = this,
185                settings = $this.data('settings'),
186                $leftGrip = $this.find(settings.left_grip_selector);
187
188            return _methods.getGripPositionPx.call($this, $leftGrip);
189         },
190         /*
191          * Return the position of the right Grip if it exists or return the
192          * current position if not. Even if the right grip doesn't exist, its
193          * position should be defined, as it determines the position of the 
194          * bar.
195          */
196         'getRightGripPositionPx' : function () {
197            var $this = this,
198                settings = $this.data('settings');
199
200                if ($this.data('has_right_grip')) {
201                    return _methods.getGripPositionPx.call($this,
202                        $this.find(settings.right_grip_selector)
203                    );
204                }
205
206                // default
207                var sliderWidthPx = _methods.getSliderWidthPx.call($this) - $this.data('left_grip_width');
208                return _methods.rangemap_0_to_n.call($this, $this.data('cur_max'), sliderWidthPx);
209         },
210         /*
211          * Return the width of the left grip.  Like getSliderWidthPx, this
212          * method deals with .width() returning a floating point number. All
213          * the code in this plugin assumes an integer here!
214          */
215         'getLeftGripWidth' : function () {
216            var $this = this,
217                settings = $this.data('settings'),
218                $leftGrip = $this.find(settings.left_grip_selector);
219
220            return Math.round($leftGrip.outerWidth());
221         },
222         /*
223          * Return the width of the right grip. The calling method should
224          * check that the right grip actually exists. This method assumes it
225          * does.
226          */
227         'getRightGripWidth' : function () {
228            var $this = this,
229                settings = $this.data('settings'),
230                $rightGrip = $this.find(settings.right_grip_selector);
231
232            return Math.round($rightGrip.outerWidth());
233         },
234         'binarySearchValueToPxCompareFunc' : function (s, a, i) {
235            // Must return:
236            //
237            // s: element to search for
238            // a: array we are looking in 
239            // i: position of the element we are looking for
240            //
241            // -1 (s < a[i])
242            // 0  found (= a[i])
243            // 1  (s > a[i])
244            if (s === a[i])          { return 0; }  // element found exactly
245            if (s < a[i] && i === 0) { return 0; }  // left extreme case e.g., a = [ 3, ... ], s = 1
246            if (a[i-1] <= s && s < a[i]) { return 0; } // s is between two elements, always return the rightmost
247            if (s > a[i])           { return 1;  }
248            if (s <= a[i-1])        { return -1; }
249            $.error('cannot compare s: ' + s + ' with a[' + i + ']. a is: ' + a.join(','));
250         },
251         /*
252          * Perform binary search to find searchElement into a generic array.
253          * It uses a customized compareFunc to perform the comparison between
254          * two elements of the array and a getElement function to pick the
255          * element from the array (e.g., in case we want to pick a field of an
256          * array of objects)
257          */
258         'binarySearch' : function(array, searchElement, getElementFunc, compareFunc) {
259              var minIndex = 0;
260              var maxIndex = array.length - 1;
261              var currentIndex;
262              var currentElement;
263
264              while (minIndex <= maxIndex) {
265                  currentIndex = (minIndex + maxIndex) / 2 | 0;
266                  currentElement = getElementFunc(array, currentIndex);
267
268                  // lt = -1 (searchElement < currentElement)
269                  // eq = 0 
270                  // gt = 1  (searchElement > currentElement)
271                  var lt_eq_gt = compareFunc(searchElement, array, currentIndex);
272
273                  if (lt_eq_gt > 0) {
274                      minIndex = currentIndex + 1;
275                  }
276                  else if (lt_eq_gt < 0) {
277                      maxIndex = currentIndex - 1;
278                  }
279                  else {
280                      return currentIndex;
281                  }
282              }
283
284              return -1;
285        },
286        /*
287         * Returns true if this slider has limit, false otherwise. There can be
288         * an upper limit and a lower limit for the sliders.
289         * The lower/upper limits are values that are out of the slider range,
290         * but that can be selected by the user when he moves a slider all the
291         * way down the minimum and up to the maximum value.
292         */
293        'haveLimits' : function () {
294            var $this = this,
295                lowerLimit = $this.data('lower-limit'),
296                upperLimit = $this.data('upper-limit'),
297                haveLimits = false;
298
299            if (typeof lowerLimit !== 'undefined' && 
300                typeof upperLimit !== 'undefined') {
301                
302                haveLimits = true;
303            }
304
305            return haveLimits;
306        },
307        /*
308         * This method is called whenever the style of the grips needs to get
309         * updated.
310         */
311        'refresh_grips_style' : function () {
312            var $this = this,
313            settings = $this.data('settings');
314
315            // Skip refreshing grips style if no hihglight is specified in
316            // construction
317            if (typeof settings.highlight === 'undefined') {
318                return;
319            }
320
321            var highlightedRangeMin = $this.data('highlightedRangeMin');
322
323            if (typeof highlightedRangeMin === 'undefined') {
324                return;
325            }
326
327            var $leftGrip = $this.find(settings.left_grip_selector),
328                $rightGrip = $this.find(settings.right_grip_selector),
329                highlightedRangeMax = $this.data('highlightedRangeMax'),
330                curMin = $this.data('cur_min'),
331                curMax = $this.data('cur_max'),
332                highlightGripClass = settings.highlight.grip_class;
333
334            // curmin is within the highlighted range
335            if (curMin < highlightedRangeMin || curMin > highlightedRangeMax) {
336                // de-highlight grip
337                $leftGrip.removeClass(highlightGripClass);
338            }
339            else {
340                // highlight grip
341                $leftGrip.addClass(highlightGripClass);
342            }
343
344            // time to highlight right grip
345            if (curMax < highlightedRangeMin || curMax > highlightedRangeMax) {
346                // de-highlight grip
347                $rightGrip.removeClass(highlightGripClass);
348            }
349            else {
350                // highlight grip
351                $rightGrip.addClass(highlightGripClass);
352            }
353        },
354        /* 
355         *  Set left and right handle at the right position on the screen (pixels) 
356         *  given the desired position in currency.
357         * 
358         *  e.g., _methods.set_position_from_val.call($this, 10000, 100000);
359         *        
360         *        may set the left handle at 100px and the right handle at
361         *        200px;
362         *   
363         */
364        'set_position_from_val' : function (cur_min, cur_max) {
365            var $this = this;
366            // 
367            // We need to understand how much pixels cur_min and cur_max
368            // correspond.
369            //
370            var range_min = $this.data('range_min'),
371                range_max = $this.data('range_max');
372
373            //
374            // (safety) constrain the cur_min or the cur_max value between the
375            // max/min ranges allowed for this slider.
376            //
377            if (cur_min < range_min) { cur_min = range_min; }
378            if (cur_min > range_max) { cur_min = range_max; }
379
380            if ($this.data('has_right_grip')) {
381                if (cur_max > range_max) { cur_max = range_max; }
382                if (cur_max < range_min) { cur_max = range_min; }
383            }
384            else {
385                cur_max = $this.data('cur_max');
386            }
387
388            var leftPx = methods.value_to_px.call($this, cur_min),
389                rightPx = methods.value_to_px.call($this, cur_max);
390
391            _methods.set_handles_at_px.call($this, leftPx, rightPx);
392
393            // save this position
394            $this.data('cur_min', cur_min);
395
396            if ($this.da
396ta('has_right_grip')) {
397                $this.data('cur_max', cur_max);
398            }
399
400            return $this;
401        },
402        /*
403         * Set the position of the handles at the specified pixel points (taking
404         * the whole slider width as a maximum point).
405         */
406        'set_position_from_px' : function (leftPx, rightPx) {
407            var $this = this;
408
409            //
410            // we need to find a value from the given value in pixels
411            //
412
413            // now set the position as requested...
414            _methods.set_handles_at_px.call($this, leftPx, rightPx);
415
416            var valueLeftRight = _methods.getSliderValuesAtPositionPx.call($this, leftPx, rightPx),
417                leftPxInValue = valueLeftRight[0],
418                rightPxInValue = valueLeftRight[1];
419
420            // ... and save the one we've found.
421            $this.data('cur_min', leftPxInValue);
422
423            if ($this.data('has_right_grip')) {
424                $this.data('cur_max', rightPxInValue);
425            }
426
427            return $this;
428        },
429        /*
430         * Updates the CSS of grips and bar so that the left grip appears at
431         * leftPx and the right grip appears at rightPx. Note: leftPx can be >
432         * rightPx.
433         */
434        'set_handles_at_px' : function (leftPx, rightPx) {
435            var $this = this;
436            var settings = $this.data('settings');
437
438            var left_grip_selector = settings.left_grip_selector,
439                right_grip_selector = settings.right_grip_selector,
440                value_bar_selector = settings.value_bar_selector;
441
442            var handleWidth = $this.data('left_grip_width');
443
444            // The left grip
445            $this.find(left_grip_selector).css('left', leftPx + 'px');
446
447            // The right grip
448            $this.find(right_grip_selector).css('left', rightPx + 'px');
449
450            // The value bar
451            if ($this.data('has_right_grip')) {
452                // If both the grips are there, the value bar must stick to
453                // beginning and the end of the grips. 
454                $this.find(value_bar_selector)
455                    .css('left', leftPx + 'px')
456                    .css('width', (rightPx - leftPx + handleWidth) + 'px');
457            }
458            else {
459                if (!_naked_bar_deltas) {
460                    _methods.populateNakedBarDeltas.call($this, leftPx, rightPx, handleWidth);
461                }
462
463                if (rightPx > leftPx) {
464                    // The naked end of the bar is on the right of the grip
465                    $this.find(value_bar_selector)
466                        .css('left', leftPx + 'px')
467                        .css('width', rightPx - leftPx + _naked_bar_deltas.toEndWidth + 'px');
468                }
469                else {
470                    // The naked end of the bar is on the left of the grip
471                    // NOTE: leftPx and rightPx are to be read swapped here.
472                    $this.find(value_bar_selector)
473                        .css('left', rightPx + _naked_bar_deltas.toBeginLeft + 'px')
474                        .css('width', (leftPx - rightPx + _naked_bar_deltas.toBeginWidth) + 'px');
475                }
476            }
477
478            return $this;
479            
480        },
481        'drag_start_func_touch' : function (e, settings, $left_grip, $right_grip, is_touch) {
482            var $this = this,
483                original_event = e.originalEvent,
484                touch = original_event.touches[0];
485
486            // for touch devices we need to make sure we allow the user to scroll
487            // if the click was too far from the slider.
488            var curY = touch.pageY,
489                curX = touch.pageX;
490
491            // is the user allowed to grab if he/she tapped too far from the
492            // slider?
493            var ydelta = Math.abs($this.offset().top - curY),
494                slider_left = $this.offset().left,
495                xldelta = slider_left - curX,
496                xrdelta = curX - (slider_left + $this.width());
497
498            if (ydelta > settings.touch_tolerance_value_bar_y  ||
499                xldelta > settings.touch_tolerance_value_bar_x ||
500                xrdelta > settings.touch_tolerance_value_bar_x ) {
501
502                return;
503            }
504
505            original_event.preventDefault();
506            _original_mousex = touch.pageX;
507
508            // true : is touch event
509            _methods.drag_start_func.call($this, touch, settings, $left_grip, 
510                $right_grip, is_touch);
511        },
512        'drag_start_func' : function (e, settings, $leftGrip, $rightGrip, 
513                is_touch) {
514
515            var $this = this;
516
517            $this.find(settings.left_grip_selector + 
518                ',' + settings.value_bar_selector + 
519                ',' + settings.right_grip_selector).removeClass(
520
521                settings.animating_css_class
522            );
523            
524            if (!methods.is_enabled.call($this)) { return; }
525        
526            //
527            // if the user used the finger, he/she is allowed to touch anywhere.
528            // but if the mouse is used, we want to enable the logic only for
529            // left grip, right grip, bar/panel elements.
530            //
531            var $target = $(e.target);
532
533            // ... if the highlight range was enabled we should check wether
534            // the user has tapped or clicked the highlight panel...
535            var targetIsPanelSelector = false;
536            if (typeof settings.highlight === 'object') {
537                targetIsPanelSelector = $target.is(settings.highlight.panel_selector);
538            }
539
540            if (is_touch === false && 
541                !$target.is(settings.left_grip_selector) &&
542                !$target.is(settings.right_grip_selector) && 
543                !$target.is(settings.value_bar_selector) &&
544                !targetIsPanelSelector &&
545                !$target.is($this) ) {
546
547                return;
548            }
549
550            // - - - -
551            // the following logic finds the nearest slider grip and starts
552            // dragging it.
553            // - - - -
554            
555            _$current_slider = $this;
556
557            var leftGripPositionPx = _methods.getGripPositionPx.call($this, $leftGrip),
558                sliderWidthPx = _methods.getSliderWidthPx.call($this) - $this.data('left_grip_width'),
559                lleft = $leftGrip.offset().left,
560                rleft, // don't compute this yet (maybe not needed if 1 grip)
561                curX,
562                ldist,
563                rdist,
564                ldelta,
565                rdelta;
566
567            var rightGripPositionPx = _methods.getRightGripPositionPx.call($this);
568
569            //
570            // We need to do as if the click happened a bit more on the left.
571            // That's because we will be setting the left CSS property at the
572            // point where the click happened, meaning the slider grip will be
573            // spanning to the right.
574            //
575            curX = Math.round(e.pageX) - ($this.data('left_grip_width') / 2);
576
577            // calculate deltas from left and right grip
578            ldist = Math.abs(lleft - curX);
579            ldelta = curX - lleft;
580
581            if ($this.data('has_right_grip')) {
582                rleft = $rightGrip.offset().left;
583                rdist = Math.abs(rleft - curX);
584                rdelta = curX - rleft;
585            }
586            else {
587                // no right grip... we make the right slider
588                // unreachable!
589                rdist = ldist * 2;
590                rdelta = ldelta * 2;
591            }
592            
593            // notify the beginning of a dragging...
594            settings.user_drag_start_callback.call($this, e);
595
596            if (ldist === rdist) {
597
598                if (curX < lleft) {
599                    // move the left grip
600                    leftGripPositionPx += ldelta;
601                    _is_left_grip = true;
602                }
603                else {
604                    // move the right grip
605                    rightGripPositionPx += rdelta;
606                    _is_left_grip = false;
607                }
608            }
609            else if (ldist < rdist) {
610
611                // move the left grip
612                leftGripPositionPx += ldelta;
613                _is_left_grip = true;
614            }
615            else {
616                // move the right grip
617                rightGripPositionPx += rdelta;
618                _is_left_grip = false;
619            }
620
621            //
622            // Limit the right grip to the maximum allowed - as the user can
623            // actually click beyond it!
624            //
625            // ...............
626            //               ^-- maximum clickable
627            //              ^--- maximum allowed (i.e., sliderWidth - gripWidth)
628            //
629            // if user clicks at sliderWidth, we will be setting CSS left of
630            // right handle having:
631            //
632            // ...............R  <-- out of bound :-(
633            //               ^-- maximum clickable
634            //              ^--- maximum allowed (i.e., sliderWidth - gripWidth)
635            //
636            // but we want:
637            //
638            // ..............R <-- within bound :-)
639            //               ^-- maximum clickable
640            //              ^--- maximum allowed (i.e., sliderWidth - gripWidth)
641            //
642            // Hence we limit.
643            //
644
645            if ($this.data('has_right_grip')) {
646                // here we check the right handle only, because it should
647                // always be the one that gets moved if the user clicks towards
648                // the right extremity!
649                if (rightGripPositionPx > sliderWidthPx) {
650                    rightGripPositionPx = sliderWidthPx;
651                }
652            }
653            else {
654                // in case we have one handle only, we will be moving the left
655                // handle instead of the right one... hence we need to perform
656                // this check on the left handle as well!
657                if (leftGripPositionPx > sliderWidthPx) {
658                    leftGripPositionPx = sliderWidthPx;
659                }
660            }
661
662            // this can happen because the user can click on the left handle!
663            // (which is out of the left boundary)
664            if (leftGripPositionPx < 0) {
665                leftGripPositionPx = 0;
666            }
667            
668            _is_mousedown = true;
669
670            var prev_min = $this.data('cur_min'),
671                prev_max = $this.data('cur_max');
672
673            _methods.set_position_from_px.call($this, leftGripPositionPx, rightGripPositionPx);
674
675            // set the style of the grips according to the highlighted range
676            _methods.refresh_grips_style.call($this);
677
678            _methods.notify_changed_implicit.call($this, 'drag_start', prev_min, prev_max);
679
680            // no need to call preventDefault on touch events, as we called
681            // preventDefault on the original event already
682            if (Object.prototype.toString.apply(e) !== "[object Touch]") {
683                e.preventDefault();
684            }
685        },
686        'drag_move_func_touch' : function (e) {
687            if (_is_mousedown === true) {
688                var original_event = e.originalEvent;
689                original_event.preventDefault();
690                var touch = original_event.touches[0];
691                _methods.drag_move_func(touch);
692            }
693        },
694        'drag_move_func' : function (e) {
695            if (_is_mousedown) {
696                // our slider element.
697                var $this = _$current_slider,
698                    settings = $this.data('settings'),
699                    sliderWidthPx = _methods.getSliderWidthPx.call($this) - $this.data('left_grip_width'),
700                    leftGripPositionPx = _methods.getLeftGripPositionPx.call($this);
701
702                var rightGripPositionPx = _methods.getRightGripPositionPx.call($this);
703
704                //
705                // Here we are going to set the position in pixels based on
706                // where the user has moved the mouse cursor. We obtain the
707                // position of the mouse cursors via e.pageX, which returns the
708                // absolute position of the mouse on the screen.
709                //
710                var absoluteMousePosition = Math.round(e.pageX);
711
712                //
713                // Compute the delta (in px) for the slider movement. It is the
714                // difference between the new position of the cursor and the
715                // old position of the cursor.
716                //
717                // Based on the delta we decide how to move the dragged handle.
718                //
719                // 0 : no movement
720                // -delta: move left
721                // +delta: move right
722                //
723                var delta = absoluteMousePosition - _original_mousex;
724
725                //
726                // User cannot drag the handles outside the slider bar area.
727                //
728
729                // 1) calculate the area within which the movement is
730                //    considered to be valid.
731                var half_a_grip_width = $this.data('left_grip_width') / 2,
732                    drag_area_start = $this.offset().left + $this.data('left_grip_width') - half_a_grip_width,
733                    drag_area_end = drag_area_start + sliderWidthPx;
734
735                if (settings.crossable_handles === false && $this.data('has_right_grip')) {
736                    // if handles are not crossable, we should define the left
737                    // and the right boundary of the movement.
738                    if (_is_left_grip) {
739                        drag_area_end = drag_area_start + rightGripPositionPx;
740                    }
741                    else {
742                        drag_area_start = drag_area_start + leftGripPositionPx;
743                    }
744                }
745 
746                // 2) by default we accept to move the slider according to both
747                // the deltas (i.e., left or right)
748                var ignore_positive_delta = 0,
749                    ignore_negative_delta = 0;
750 
751                // 3) but if the user is moving the mouse beyond the draggable
752                // area, we should only accept a movement in one direction.
753                if (absoluteMousePosition < drag_area_start) { 
754                    ignore_positive_delta = 1;
755                    ignore_negative_delta = 0;
756                }
757                if (absoluteMousePosition > drag_area_end) {
758                    ignore_negative_delta = 1;
759                    ignore_positive_delta = 0;
760                }
761
762                //
763                // Here we decide whether to invert the grip being moved.
764                //
765                if (settings.crossable_handles === true && 
766                    $this.data('has_right_grip')) {
767
768                    if (_is_left_grip) {
769
770                        // ... if we are using the left grip
771                        if (rightGripPositionPx <= sliderWidthPx) {
772
773                            // the inversion logic should only be active when the
774                            // slider is not at the extremity
775                            if (leftGripPositionPx + delta > rightGripPositionPx) {
776
777                                _is_left_grip = false;
778
779                                // TWEAK: keep the position of the left handle fixed
780                                // at the one of the right handle as the user may
781                                // have moved the mouse too fast, thus giving
782                                // leftGripPositionPx > rightGripPositionPx.
783                                //
784                                // Basically here we avoid:
785                                // 
786                                // Initial State:
787                                //
788                                // ------L-R------  (leftGripPositionPx < rightGripPositionPx)
789                                //
790                                // Fast Mouse Move:
791                                //
792                                // --------R--L---  (leftGripPositionPx + delta)
793                                // --------R-L----  (leftGripPositionPx [ still > rightGripPositionPx! ])
794                                //
795                                // _is_left_grip becomes false (this code)
796                                // 
797                                leftGripPositionPx = rightGripPositionPx;
798                            }
799                        }
800                    }
801                    else {
802                        // ... converse logic
803                        if (leftGripPositionPx >= 0) {
804                            if (rightGripPositionPx + delta < leftGripPositionPx) {
805
806                                // current_max = current_min;
807                                _is_left_grip = true;
808
809                                rightGripPositionPx = leftGripPositionPx;
810                            }
811                        }
812                    }
813                }
814
815                //
816                // Decide the position of the new handles.
817                //
818                var nextLeftGripPositionPx = leftGripPositionPx,
819                    nextRightGripPositionPx = rightGripPositionPx;
820
821                if ((delta > 0 && !ignore_positive_delta) || 
822                    (delta < 0 && !ignore_negative_delta)) {
823
824                    if (_is_left_grip) {
825                        nextLeftGripPositionPx += delta;
826                    }
827                    else {
828                        nextRightGripPositionPx += delta;
829                    }
830                }
831
832                _methods.validateAndMoveGripsToPx.call($this, nextLeftGripPositionPx, nextRightGripPositionPx);
833 
834                // prepare for next movement
835                _original_mousex = absoluteMousePosition;
836
837                
838                // no need to call preventDefault on touch events, as we called
839                // preventDefault on the original event already
840                if (Object.prototype.toString.apply(e) !== "[object Touch]") {
841                    e.preventDefault();
842                }
843            }
844        },
845        'drag_end_func_touch' : function (e) {
846            var original_event = e.originalEvent;
847            original_event.preventDefault();
848            var touch = original_event.touches[0];
849            _methods.drag_end_func(touch);
850        },
851        'drag_end_func' : function (/* e */) {
852            var $this = _$current_slider;
853            if (typeof $this !== 'undefined') {
854                _is_mousedown = false;
855                _original_mousex = undefined;
856
857                _methods.notify_mouse_up_implicit.call($this, _is_left_grip);
858
859                // require another click on a handler before going into here again!
860                _$current_slider = undefined;
861
862                // put back the class once user finished dragging
863                var settings = $this.data('settings');
864                $this.find(settings.left_grip_selector + 
865                    ',' + settings.value_bar_selector + 
866                    ',' + settings.right_grip_selector).addClass(
867
868                    settings.animating_css_class
869                );
870            }
871        },
872        'get_rounding_for_value' : function (v) {
873            var $this = this;
874            var rounding = $this.data('rounding');
875            var rounding_ranges = $this.data('rounding_ranges');
876
877            if (typeof rounding_ranges === 'object') {
878
879                // then it means the rounding is not fixed, we should find the
880                // value in the roundings_array.
881                var roundingIdx  = _methods.binarySearch.call($this, roun
881ding_ranges, v, 
882                    // pick an element from the array
883                    function (array, index) { return array[index].range; },
884
885                    // compare search element with current element
886                    // < 0 search < current
887                    // 0   equals
888                    // > 0 search > current
889                    function (search, array, currentIdx) {
890
891                        // first check if this is our element
892
893                        // this is our element if the search value is:
894                        if (search < array[currentIdx].range) {
895
896                            // we can have a match or search in the left half
897                            if (currentIdx > 0) {
898                                if (search >= array[currentIdx - 1].range) {
899                                    return 0;
900                                }
901                                else {
902                                    // go left
903                                    return -1;
904                                }
905                            }
906                            else {
907                                return 0;
908                            }
909                        }
910                        else {
911                            // we must search in the next half
912                            return 1;
913                        }
914                    }
915                );
916
917                rounding = 1;
918                if (roundingIdx > -1) {
919                    rounding = parseInt(rounding_ranges[roundingIdx].value, 10);
920                }
921                else {
922                    var lastIdx = rounding_ranges.length - 1;
923                    if (v >= rounding_ranges[lastIdx].range) {
924                        rounding = rounding_ranges[lastIdx].value;
925                    }
926                }
927            }
928            return rounding;
929        },
930        /*
931         * Calls the user mouseup callback with the right parameters. Relies on
932         * $data('beforestart_min/max') in addition to the isLeftGrip parameter.
933         *
934         * NOTE: saves the new beforestart_min and begforestart_max as well.
935         */
936        'notify_mouse_up_implicit' : function(isLeftGrip) {
937            var $this = this,
938                current_min_value = methods.get_current_min_value.call($this),
939                current_max_value = methods.get_current_max_value.call($this),
940                didValuesChange = false;
941
942            // check if we changed.
943            if (($this.data('beforestart_min') !== current_min_value) || 
944                ($this.data('beforestart_max') !== current_max_value)
945            ) {
946                // values have changed!
947                didValuesChange = true;
948
949                // save the new values
950                $this.data('beforestart_min', current_min_value);
951                $this.data('beforestart_max', current_max_value);
952            }
953
954
955            var settings = $this.data('settings');
956
957            settings.user_mouseup_callback.call($this, 
958                methods.get_current_min_value.call($this),
959                methods.get_current_max_value.call($this),
960                isLeftGrip,
961                didValuesChange
962            );
963
964            return $this;
965        },
966        /*
967         * NOTE: this method may take the previous min/max value as input.
968         *       if no arguments are provided the method blindly notifies.
969         */
970        'notify_changed_implicit' : function (cause, prevMin, prevMax) {
971            var $this = this;
972
973            var force = false;
974            if (cause === 'init' || cause === 'refresh') {
975                force = true;
976            }
977
978            var curMin = methods.get_current_min_value.call($this),
979                curMax = methods.get_current_max_value.call($this);
980
981            if (!force) {
982                prevMin = methods.round_value_according_to_rounding.call($this, prevMin);
983                prevMax = methods.round_value_according_to_rounding.call($this, prevMax);
984            }
985
986            if (force || curMin !== prevMin || curMax !== prevMax) {
987
988                _methods.notify_changed_explicit.call($this, cause, prevMin, prevMax, curMin, curMax);
989
990                force = 1;
991            }
992
993            return force;
994        },
995        'notify_changed_explicit' : function (cause, prevMin, prevMax, curMin, curMax) {
996            var $this = this,
997                settings = $this.data('settings');
998
999            // maybe update aria attributes for accessibility
1000            if ($this.data('aria_enabled')) {
1001                _methods.updateAriaAttributes.call($this);
1002            }
1003
1004            settings.value_changed_callback.call($this, cause, curMin, curMax, prevMin, prevMax);
1005
1006            return $this;
1007        },
1008        'validate_params' : function (settings) {
1009            var $this = this;
1010            var min_value = $this.data('range_min'),
1011                max_value = $this.data('range_max'),
1012                cur_min = $this.data('cur_min'),
1013                lower_limit = $this.data('lower-limit'),
1014                upper_limit = $this.data('upper-limit');
1015
1016            var have_limits = _methods.haveLimits.call($this);
1017
1018            if (typeof min_value === 'undefined') {
1019                $.error("the data-range_min attribute was not defined");
1020            }
1021            if (typeof max_value === 'undefined') {
1022                $.error("the data-range_max attribute was not defined");
1023            }
1024            if (typeof cur_min === 'undefined') {
1025                $.error("the data-cur_min attribute must be defined");
1026            }
1027            if (min_value > max_value) {
1028                $.error("Invalid input parameter. must be min < max");
1029            }
1030
1031            if (have_limits && lower_limit > upper_limit) {
1032                $.error('Invalid data-lower-limit or data-upper-limit');
1033            }
1034            if ($this.find(settings.left_grip_selector).length === 0) {
1035                $.error("Cannot find element pointed by left_grip_selector: " + settings.left_grip_selector);
1036            }
1037            /* 
1038             * NOTE: only validate right grip selector if it is not
1039             * undefined otherwise just assume that if it isn't
1040             * found isn't there. This is because we initialize the
1041             * slider at once and let the markup decide if the
1042             * slider is there or not.
1043             */
1044            if (typeof settings.right_grip_selector !== 'undefined') {
1045                if ($this.find(settings.right_grip_selector).length === 0) {
1046                    $.error("Cannot find element pointed by right_grip_selector: " + settings.right_grip_selector);
1047                }
1048            }
1049
1050            // same thing for the value bar selector
1051            if (typeof settings.value_bar_selector !== 'undefined') {
1052                if ($this.find(settings.value_bar_selector).length === 0) {
1053                    $.error("Cannot find element pointed by value_bar_selector" + settings.value_bar_selector);
1054                }
1055            }
1056        },
1057        /*
1058         * Maps a value between [minRange -- maxRange] into [0 -- n].
1059         * The target range will be an integer number.
1060         */
1061        'rangemap_0_to_n' : function (val, n) {
1062            var $this = this;
1063            var rangeMin = $this.data('range_min');
1064            var rangeMax = $this.data('range_max');
1065
1066            if (val <= rangeMin) { return 0; }
1067            if (val >= rangeMax) { return n; }
1068
1069            return Math.floor((n * val - n * rangeMin) / (rangeMax - rangeMin));
1070        },
1071        /*
1072         * Maps a value between [0 -- max] back into [minRange -- maxRange].
1073         * The target range can be a floating point number.
1074         */
1075        'inverse_rangemap_0_to_n' : function (val, max) {
1076            var $this = this;
1077            var rangeMin = $this.data('range_min');
1078            var rangeMax = $this.data('range_max');
1079
1080            if (val <= 0)   { return rangeMin; }
1081            if (val >= max) { return rangeMax; }
1082
1083            //
1084            // To do this we first map 0 -- max relatively withing [minRange
1085            // and maxRange], that is between [0 and (maxRange-minRange)].
1086            //
1087            var relativeMapping = (rangeMax - rangeMin) * val / max;
1088
1089            // ... then we bring this to the actual value by adding rangeMin.
1090            return relativeMapping + rangeMin;
1091        }
1092
1093    };
1094    var methods = {
1095        
1096        'teardown' : function () {
1097            var $this = this;
1098
1099            // remove all data set with .data()
1100            $this.removeData();
1101
1102            // unbind the document as well
1103            $(document)
1104                .unbind('mousemove.nstSlider')
1105                .unbind('mouseup.nstSlider');
1106
1107            // unbind events bound to the container element
1108            $this.parent()
1109                .unbind('mousedown.nstSlider')
1110                .unbind('touchstart.nstSlider')
1111                .unbind('touchmove.nstSlider')
1112                .unbind('touchend.nstSlider');
1113            
1114            // unbind events bound to the current element
1115            $this.unbind('keydown.nstSlider')
1116                .unbind('keyup.nstSlider');
1117
1118            return $this;
1119        },
1120        'init' : function(options) {
1121            var settings = $.extend({
1122                'animating_css_class' : 'nst-animating',
1123                // this is the distance from the value bar by which we should
1124                // grab the left or the right handler.
1125                'touch_tolerance_value_bar_y': 30,  // px
1126                'touch_tolerance_value_bar_x': 15,  // px
1127                // where is the left grip?
1128                'left_grip_selector': '.nst-slider-grip-left',
1129                // where is the right grip?
1130                // undefined = (only left grip bar)
1131                'right_grip_selector': undefined,
1132
1133                // Specify highlight like this if you want to highlight a range
1134                // in the slider.
1135                //
1136                // 'highlight' : {
1137                //     'grip_class' : '.nsti-slider-hi',
1138                //     'panel_selector' : '.nst-slider-highlight-panel'
1139                // },
1140                'highlight' : undefined,
1141
1142                // Lets you specify the increment rounding for the slider handles
1143                // for when the user moves them.
1144                // It can be a string, indicating a fixed increment, or an object
1145                // indicating the increment based on the value to be rounded.
1146                //
1147                // This can be specified in the following form: {
1148                //    '1' : '100',    
1149                //    '10' : '1000',  /* rounding = 10 for values in [100-999] */
1150                //    '50' : '10000',
1151                // }
1152                'rounding': undefined,
1153
1154                // if the bar is not wanted
1155                'value_bar_selector': undefined,
1156
1157                // Allow handles to cross each other while one of them is being
1158                // dragged. This option is ignored if just one handle is used.
1159                'crossable_handles': true,
1160
1161                'value_changed_callback': function(/*cause, vmin, vmax*/) { return; },
1162                'user_mouseup_callback' : function(/*vmin, vmax, left_grip_moved*/) { return; },
1163                'user_drag_start_callback' : function () { return; }
1164            }, options);
1165
1166            //
1167            // we need to unbind events attached to the document,
1168            // as if we replace html elements and re-initialize, we
1169            // don't want to double-bind events!
1170            //
1171            var $document = $(document);
1172
1173            // make sure only one event is bound to the document
1174            $document.unbind('mouseup.nstSlider');
1175            $document.unbind('mousemove.nstSlider');
1176
1177            $document.bind('mousemove.nstSlider', _methods.drag_move_func);
1178            $document.bind('mouseup.nstSlider',   _methods.drag_end_func);
1179
1180            return this.each(function() {
1181                //
1182                // $this is like:
1183                //
1184                // <div class="outer-slider" data-... data-...>
1185                //     <div class="bar"></div>
1186                //     <div class="leftGrip"></div>
1187                //     <div class="rightGrip"></div>
1188                // </div>
1189                //
1190                // It is supposed to be enclosed in a container
1191                //
1192                var $this = $(this),
1193                    $container = $this.parent();
1194
1195                // enable: the user is able to move the grips of this slider.
1196                $this.data('enabled', true);
1197
1198                // fix some values first
1199                var rangeMin = $this.data('range_min'),
1200                    rangeMax = $this.data('range_max'),
1201                    valueMin = $this.data('cur_min'),
1202                    valueMax = $this.data('cur_max');
1203
1204                // assume 0 if valueMax is not specified
1205                if (typeof valueMax === 'undefined') {
1206                    valueMax = valueMin;
1207                }
1208
1209                if (rangeMin === '') { rangeMin = 0; }
1210                if (rangeMax === '') { rangeMax = 0; }
1211                if (valueMin === '') { valueMin = 0; }
1212                if (valueMax === '') { valueMax = 0; }
1213
1214                $this.data('range_min', rangeMin);
1215                $this.data('range_max', rangeMax);
1216                $this.data('cur_min', valueMin);
1217                $this.data('cur_max', valueMax);
1218
1219                // halt on error
1220                _methods.validate_params.call($this, settings);
1221
1222                $this.data('settings', settings);
1223
1224                // override rounding from markup if defined in configuration
1225                if (typeof settings.rounding !== 'undefined') {
1226                    methods.set_rounding.call($this, settings.rounding);
1227                }
1228                else if (typeof $this.data('rounding') !== 'undefined') {
1229                    methods.set_rounding.call($this, $this.data('rounding'));
1230                }
1231                else {
1232                    methods.set_rounding.call($this, 1);
1233                }
1234                
1235                var left_grip = $this.find(settings.left_grip_selector)[0],
1236                    $left_grip = $(left_grip),
1237                    $right_grip = $($this.find(settings.right_grip_selector)[0]);
1238
1239                // make sure left grip can be tabbed if the user hasn't
1240                // defined their own tab index
1241                if (typeof $left_grip.attr('tabindex') === 'undefined') {
1242                    $left_grip.attr('tabindex', 0);
1243                }
1244
1245                // no right handler means single handler
1246                var has_right_grip = false;
1247                if ($this.find(settings.right_grip_selector).length > 0) {
1248                    has_right_grip = true;
1249
1250                    // make sure right grip can be tabbed if the user hasn't
1251                    // defined their own tab index
1252                    if (typeof $right_grip.attr('tabindex') === 'undefined') {
1253                        $right_grip.attr('tabindex', 0);
1254                    }
1255                }
1256                $this.data('has_right_grip', has_right_grip);
1257
1258                // enable aria attributes update?
1259                if ($this.data('aria_enabled') === true) {
1260                    // setup aria role attributes on each grip
1261                    $left_grip
1262                        .attr('role', 'slider')
1263                        .attr('aria-disabled', 'false');
1264
1265                    if (has_right_grip) {
1266                        $right_grip
1267                            .attr('role', 'slider')
1268                            .attr('aria-disabled', 'false');
1269                    }
1270                }
1271
1272                //
1273                // deal with keypresses here
1274                //
1275                $this.bind('keyup.nstSlider', function (e) {
1276                    if ($this.data('enabled')) {
1277                        switch (e.which) {
1278                            case 37:   // left
1279                            case 38:   // up
1280                            case 39:   // right 
1281                            case 40:   // down
1282
1283                            if (_before_keydown_value === _before_keyup_value) {
1284
1285                                // we should search for the next value change...
1286                                // ... in which direction? depends on whe
1287
1288                                var searchUntil = _methods.getSliderWidthPx.call($this),
1289                                    val,
1290                                    i,
1291                                    setAtPixel;
1292
1293                                if (_before_keydown_pixel - _before_keyup_pixel < 0) {
1294                                    // the grip was moved towards the right
1295
1296                                    for (i=_before_keyup_pixel; i<=searchUntil; i++) {
1297                                        // if the value at pixel i is different than
1298                                        // the current value then we are good to go.
1299                                        //
1300                                        val = methods.round_value_according_to_rounding.call($this,
1301                                            _methods.getSliderValuesAtPositionPx.call($this, i, i)[1]
1302                                        );
1303                                        if (val !== _before_keyup_value) {
1304                                            setAtPixel = i;
1305                                            break;
1306                                        }
1307                                    }
1308                                }
1309                                else {
1310                                    // the grip was moved towards the left
1311
1312                                    for (i=_before_keyup_pixel; i>=0; i--) {
1313
1314                                        // if the value at pixel i is different than
1315                                        // the current value then we are good to go.
1316                                        //
1317                                        val = methods.round_value_according_to_rounding.call($this,
1318                                            _methods.getSliderValuesAtPositionPx.call($this, i, i)[1]
1319                                        );
1320                                        if (val !== _before_keyup_value) {
1321                                           setAtPixel = i;
1322                                           break;
1323                                        }
1324                                    }
1325                                }
1326
1327
1328                                // we need to set the slider at this position
1329                                if (_is_left_grip) {
1330                                    _methods.validateAndMoveGripsToPx.call($this, setAtPixel, _methods.getRightGripPositionPx.call($this));
1331                                }
1332                                else {
1333                                    _methods.validateAndMoveGripsToPx.call($this, _methods.getLeftGripPositionPx.call($this), setAtPixel);
1334                                }
1335
1336                                //
1337                                // call the mouseup callback when the key is up!
1338                                //
1339                                _methods.notify_mouse_up_implicit.call($this, _is_left_grip);
1340                            }
1341                        }
1342
1343                        // clear values 
1344                        _before_keydown_value = undefined;
1345                        _before_keydown_pixel = undefined;
1346                        _before_keyup_value = undefined;
1347                        _before_keyup_pixel = undefined;
1348                    }
1349                });
1350                $this.bind('keydown.nstSlider', function (evt) {
1351                    if ($this.data('enabled')) {
1352
1353                        var moveHandleBasedOnKeysFunc = function ($grip, e) {
1354
1355                            var nextLeft = _methods.getLeftGripPositionPx.call($this),
1356                                nextRight = _methods.getRightGripPositionPx.call($this);
1357
1358                            if (typeof _before_keydown_value === 'undefined') {
1359                                _before_keydown_pixel = _is_left_grip ? nextLeft : nextRight;
1360
1361                                _before_keydown_value = _is_left_grip ? methods.get_current_min_value.call($this) : methods.get_current_max_value.call($this);
1362                            }
1363
1364                            switch (e.which) {
1365                                case 37:   // left
1366                                case 40:   // down
1367                                    if (_is_left_grip) { nextLeft--; } else { nextRight--; }
1368                                    e.preventDefault();
1369                                    break;
1370
1371                                case 38:   // up
1372                                case 39:   // right 
1373                                    if (_is_left_grip) { nextLeft++; } else { nextRight++; }
1374
1375                                    e.preventDefault();
1376                                    break;
1377                            }
1378
1379                            _before_keyup_pixel = _is_left_grip ?  nextLeft : nextRight;
1380
1381                            // may write into cur_min, cur_max data...
1382                            _methods.validateAndMoveGripsToPx.call($this, nextLeft, 
1383                                nextRight);
1384
1385                            _before_keyup_value = _is_left_grip ? methods.get_current_min_value.call($this) : methods.get_current_max_value.call($this);
1386                        };
1387                        
1388                        // default
1389                        if (has_right_grip && $this.find(':focus').is($right_grip)) {
1390                            _is_left_grip = false;
1391                            moveHandleBasedOnKeysFunc.call($this, $right_grip, evt);
1392                        }
1393                        else {
1394                            _is_left_grip = true;
1395                            moveHandleBasedOnKeysFunc.call($this, $left_grip, evt);
1396                        }
1397                    } 
1398                });
1399
1400                // determine size of grips
1401                var left_grip_width = _methods.getLeftGripWidth.call($this),
1402                    right_grip_width = has_right_grip ? 
1403                        _methods.getRightGripWidth.call($this) : left_grip_width;
1404
1405                $this.data('left_grip_width', left_grip_width);
1406                $this.data('right_grip_width', right_grip_width);
1407
1408                $this.data('value_bar_selector', settings.value_bar_selector);
1409
1410                // set behaviour of naked bar in case of one handle
1411                if (!has_right_grip) {
1412                    var bStickToSides = valueMax === rangeMax || valueMax === rangeMin;
1413                    _methods.setNakedBarDelta.call($this,
1414                        bStickToSides ? "stickToSides" : "middle",
1415                        left_grip_width
1416                    );
1417                }
1418
1419                // this will set the range to the right extreme in such a case.
1420                if (rangeMin === rangeMax || valueMin === valueMax) {
1421                    methods.set_range.call($this, rangeMin, rangeMax);
1422                }
1423                else {
1424
1425                    // set the initial position
1426                    _methods.set_position_from_val.call($this, 
1427                        $this.data('cur_min'), $this.data('cur_max'));
1428                }
1429
1430                _methods.notify_changed_implicit.call($this, 'init');
1431
1432                // handle mouse movement
1433                $this.data('beforestart_min', methods.get_current_min_value.call($this));
1434                $this.data('beforestart_max', methods.get_current_max_value.call($this));
1435
1436                // pass a closure, so that 'this' will be the current slider bar,
1437                // not the container.
1438                $this.bind('mousedown.nstSlider', function (e) {
1439                    _methods.drag_start_func.call($this, e, settings, $left_grip, $right_grip, false);
1440                });
1441                
1442                $container.bind('touchstart.nstSlider', function (e) {
1443                    _methods.drag_start_func_touch.call($this, e, settings, $left_grip, $right_grip, true);
1444                });
1445                $container.bind('touchend.nstSlider',  function (e) {
1446                    _methods.drag_end_func_touch.call($this, e);
1447                });
1448                $container.bind('touchmove.nstSlider', function (e) {
1449                    _methods.drag_move_func_touch.call($this, e);
1450                });
1451
1452                // if the data-histogram attribute exists, then use this
1453                // histogram to set the step distribution
1454                var step_histogram = $this.data('histogram');
1455                if (typeof step_histogram !== 'undefined') {
1456                    methods.set_step_histogram.call($this, step_histogram);
1457                }
1458            }); // -- each slider
1459        },
1460        'get_range_min' : function () {
1461            var $this = this;
1462            return $this.data('range_min');
1463        },
1464        'get_range_max' : function () {
1465            var $this = this;
1466            return $this.data('range_max');
1467        },
1468        'get_current_min_value' : function () {
1469            var $this = $(this);
1470
1471            var rangeMin = methods.get_range_min.call($this),
1472                rangeMax = methods.get_range_max.call($this);
1473
1474            var currentMin = $this.data('cur_min');
1475
1476            var min;
1477            if (rangeMin >= currentMin) {
1478                min = rangeMin;
1479            }
1480            else {
1481                min = methods.round_value_according_to_rounding.call($this, currentMin);
1482            }
1483
1484            if (_methods.haveLimits.call($this)) {
1485                if (min <= rangeMin) {
1486                    return $this.data('lower-limit');
1487                }
1488                else if (min >= rangeMax) {
1489                    return $this.data('upper-limit');
1490                }
1491            }
1492            else {
1493                if (min <= rangeMin) {
1494                    return rangeMin;
1495                }
1496                else if (min >= rangeMax) {
1497                    return rangeMax;
1498                }
1499            }
1500
1501            return min;
1502        },
1503        'get_current_max_value' : function () {
1504            var $this = $(this);
1505
1506            var rangeMin = methods.get_range_min.call($this),
1507                rangeMax = methods.get_range_max.call($this);
1508
1509            var currentMax = $this.data('cur_max');
1510
1511            var max;
1512            if (rangeMax <= currentMax) {
1513                max = rangeMax;
1514            }
1515            else {
1516                max = methods.round_value_according_to_rounding.call($this, currentMax);
1517            }
1518
1519
1520            if (_methods.haveLimits.call($this)) {
1521                if (max >= rangeMax) {
1522                    return $this.data('upper-limit');
1523                }
1524                else if (max <= rangeMin) {
1525                    return $this.data('lower-limit');
1526                }
1527            }
1528            else {
1529                if (max >= rangeMax) {
1530                    return rangeMax;
1531                }
1532                else if (max <= rangeMin) {
1533                    return rangeMin;
1534                }
1535            }
1536
1537            return max;
1538        },
1539        'is_handle_to_left_extreme' : function () {
1540            var $this = this; 
1541            if (_methods.haveLimits.call($this)) {
1542                return $this.data('lower-limit') === methods.get_current_min_value.call($this);
1543            }
1544            else {
1545                return methods.get_range_min.call($this) === methods.get_current_min_value.call($this);
1546            }
1547        },
1548        'is_handle_to_right_extreme' : function () {
1549            var $this = this; 
1550            if (_methods.haveLimits.call($this)) {
1551                return $this.data('upper-limit') === methods.get_current_max_value.call($this);
1552            }
1553            else {
1554                return methods.get_range_max.call($this) === methods.get_current_max_value.call($this);
1555            }
1556        },
1557        // just call set_position on the current values
1558        'refresh' : function () {
1559            var $this = this;
1560
1561            // re-set the slider step if specified
1562            var lastStepHistogram = $this.data('last_step_histogram');
1563            if (typeof lastStepHistogram !== 'undefined') {
1564                methods.set_step_histogram.call($this, lastStepHistogram); 
1565            }
1566
1567            // re-center given values
1568            _methods.set_position_from_val.call($this, 
1569                methods.get_current_min_value.call($this),
1570                methods.get_current_max_value.call($this)
1571            );
1572
1573            // re-highlight the range
1574            var highlightRangeMin = $this.data('highlightedRangeMin');
1575            if (typeof highlightRangeMin === 'number') {
1576                // a highlight range is present, we must update it
1577                var highlightRangeMax = $this.data('highlightedRangeMax');
1578                methods.highlight_range.call($this, highlightRangeMin, highlightRangeMax);
1579            }
1580
1581            _methods.notify_changed_implicit.call($this, 'refresh');
1582            return $this;
1583        },
1584        'disable' : function () {
1585            var $this = this,
1586                settings = $this.data('settings');
1587
1588            $this.data('enabled', false)
1589                .find(settings.left_grip_selector)
1590                    .attr('aria-disabled', 'true')
1591                .end()
1592                .find(settings.right_grip_selector)
1593                    .attr('aria-disabled', 'true');
1594
1595            return $this;
1596        },
1597        'enable' : function() {
1598            var $this = this,
1599                settings = $this.data('settings');
1600
1601            $this.data('enabled', true)
1602                .find(settings.left_grip_selector)
1603                    .attr('aria-disabled', 'false')
1604                .end()
1605                .find(settings.right_grip_selector)
1606                    .attr('aria-disabled', 'false');
1607
1608            return $this;
1609        },
1610        'is_enabled' : function() {
1611            var $this = this;
1612            return $this.data('enabled');
1613        },
1614        /*
1615         * This one is the public method, called externally.
1616         * It sets the position and notifies in fact.
1617         */
1618        'set_position' : function(min, max) {
1619            var $this = this;
1620
1621            var prev_min = $this.data('cur_min'),
1622                prev_max = $this.data('cur_max');
1623
1624            if (min > max) {
1625                _methods.set_position_from_val.call($this, max, min);
1626            }
1627            else {
1628                _methods.set_position_from_val.call($this, min, max);
1629            }
1630
1631            // set the style of the grips according to the highlighted range
1632            _methods.refresh_grips_style.call($this);
1633
1634            _methods.notify_changed_implicit.call($this, 'set_position', prev_min, prev_max);
1635            
1636            // this is for the future, therefore "before the next
1637            // interaction starts"
1638            $this.data('beforestart_min', min);
1639            $this.data('beforestart_max', max);
1640        },
1641        /*
1642         * This tells the slider to increment its step non linearly over the
1643         * current range, based on the histogram on where results are.
1644         *
1645         * the input parameter 'histogram' identifies an empirical probability
1646         * density function (PDF).
1647         *
1648         */
1649        'set_step_histogram' : function (histogram) {
1650            var $this = this;
1651
1652            $this.data('last_step_histogram', histogram);
1653
1654            if (typeof histogram === 'undefined') {
1655                $.error('got an undefined histogram in set_step_histogram');
1656                _methods.unset_step_histogram.call($this);
1657            }
1658
1659            var sliderWidthPx = _methods.getSliderWidthPx.call($this) - $this.data('left_grip_width'),
1660                nbuckets = histogram.length;
1661
1662            if (sliderWidthPx <= 0) {
1663                // that means the slider is not visible...
1664                return;
1665            }
1666
1667            //
1668            // we need to transform this pdf into a cdf, and use it to obtain
1669            // two mappings: pixel to value and value to pixel.
1670            //
1671            // 1) normalize the pdf to sum to sliderWidthPx first
1672            var i;
1673            var histogram_sum = 0;
1674            for (i=0; i<nbuckets; i++) {
1675                histogram_sum += histogram[i]; 
1676            }
1677
1678            //
1679            // if the sum of the histogram is 0 it means that all is 0 in the 
1680            // histogram! (i.e, flat histogram). In this case we already know
1681            // what's going to be the answer...
1682            //
1683            if (histogram_sum === 0) {
1684                // ... and the answer is: a linear scale between min_range and
1685                // max range!
1686                methods.unset_step_histogram.call($this);
1687
1688                return $this;
1689            }
1690
1691            // coefficient for normalization
1692            var coeff = parseFloat(histogram_sum)/sliderWidthPx;
1693
1694            // go normalize the histogram using this coefficient!
1695            for (i=0; i<nbuckets; i++) {
1696                histogram[i] = histogram[i]/coeff;
1697            }
1698
1699            // 2) now that the histogram is normalized, extract the cumulative
1700            // distribution function (CDF). This is an always increasing function
1701            // that ranges between 0 and sliderWidthPx;
1702            //
1703            // We also build the inverted cdf, just the cdf read the other way
1704            // around.
1705            //
1706            var cdf = [ histogram[0] ];  // points to pixels
1707            for (i=1; i<nbuckets; i++) {
1708                var cdf_x = cdf[i-1] + histogram[i];
1709                cdf.push(cdf_x);
1710            }
1711            cdf.push(sliderWidthPx);
1712
1713
1714            // the first value here is always min_range as the cdf is supposed
1715            // to start from 0 (also first pixel = min_range)
1716            var pixel_to_value_lookup = [ $this.data('range_min') ];
1717
1718            var last_filled = 0; // we've already filled 0
1719
1720            // now stretch over the rest of the cdf
1721            var last_price_for_cdf_bucket = pixel_to_value_lookup[0];
1722
1723            var cdf_bucket_count = 0;
1724            while (last_filled <= sliderWidthPx) { // do until all pixels are filled
1725
1726                // get next item from cdf
1727                var fill_up_to_px = parseInt(cdf.shift(), 10);
1728                var price_for_cdf_bucket = 
1729                    _methods.inverse_rangemap_0_to_n.call($this, cdf_bucket_count+1, nbuckets+1);
1730
1731                cdf_bucket_count++;
1732
1733                // how many pixels do we have to fill
1734                var fill_tot = fill_up_to_px - last_filled;
1735
1736                // interpolate and fill
1737                var diff = price_for_cdf_bucket - last_price_for_cdf_bucket;
1738                for (i = last_filled; i < fill_up_to_px; i++) {
1739                    var next_price_for_cdf_bucket = 
1740                        last_price_for_cdf_bucket + (diff * (i-last_filled+1) / fill_tot);
1741
1742                    pixel_to_value_lookup.push(next_price_for_cdf_bucket);
1743
1744                    last_filled++;
1745
1746                    last_price_for_cdf_bucket = next_price_for_cdf_bucket;
1747                }
1748
1749                if (last_filled === sliderWidthPx) {
1750                    break;
1751                }
1752            }
1753            pixel_to_value_lookup[pixel_to_value_lookup.length-1] = $this.data('range_max');
1754
1755            // 3) build lookup functions to extract pixels and values from the
1756            // cdf and the inverted cdf.
1757            //
1758            var pixel_to_value_mapping = function (pixel) {
1759                return pixel_to_value_lookup[parseInt(pixel, 10)];
1760            };
1761
1762            var value_to_pixel_mapping = function (value) {
1763                //
1764                // Binary search into the array of pixels, returns always the
1765                // rightmost pixel if there is no exact match.
1766                //
1767                var suggestedPixel = _methods.binarySearch.call($this, pixel_to_value_lookup, value, 
1768                    function(a, i) { return a[i]; },  // access a value in the array
1769                    _methods.binarySearchValueToPxCompareFunc
1770                );
1771
1772                // exact match
1773                if (pixel_to_value_lookup[suggestedPixel] === value) {
1774                    return suggestedPixel;
1775                }
1776
1777                // approx match: we need to check if it's closer to the value
1778                // at suggestedPixel or the value at suggestedPixel-1
1779                if ( Math.abs(pixel_to_value_lookup[suggestedPixel-1] - value) <
1780                     Math.abs(pixel_to_value_lookup[suggestedPixel] - value) ) {
1781
1782                     return suggestedPixel-1;
1783                }
1784                return suggestedPixel;
1785            };
1786
1787            //
1788            // these two functions will be stored and then used internally to
1789            // decide what value to display given a certain pixel, and what
1790            // pixel to put the slider on given a certain value.
1791            //
1792            $this.data('pixel_to_value_mapping', pixel_to_value_mapping);
1793            $this.data('value_to_pixel_mapping', value_to_pixel_mapping);
1794
1795            return $this;
1796        },
1797        /*
1798         * Remove the pixel-to-value and the value-to-pixel mappings from the
1799         * slider so that the slider can follow a linear step over the current
1800         * range again.
1801         */
1802        'unset_step_histogram' : function () {
1803            var $this = this;
1804
1805            $this.removeData('pixel_to_value_mapping');
1806            $this.removeData('value_to_pixel_mapping');
1807            $this.removeData('last_step_histogram');
1808
1809            return $this;
1810        },
1811        'set_range' : function (rangeMin, rangeMax) {
1812            var $this = this;
1813
1814            // get the current values
1815            var oldMin = methods.get_current_min_value.call($this),
1816                oldMax = methods.get_current_max_value.call($this);
1817            
1818            // set range
1819            $this.data('range_min', rangeMin);
1820            $this.data('range_max', rangeMax);
1821
1822            // try to re-center old values in the new range.
1823            // NOTE: this may set different values!
1824            _methods.set_position_from_val.call($this, oldMin, oldMax);
1825
1826            /*
1827             * Re-highlight ranges if any are defined.
1828             */
1829            // var highlightRangeMin = $this.data('highlightedRangeMin');
1830            // if (typeof rangeMin === 'number') {
1831            //     // a highlight range is present, we must update it
1832            //     var highlightRangeMax = $this.data('highlightedRangeMax');
1833            //     methods.highlight_range.call($this, highlightRangeMin, highlightRangeMax);
1834            // }
1835
1836            // pass old min and max in the notify_changed_implicit method, so that we
1837            // notify if we need to
1838            _methods.notify_changed_implicit.call($this, 'set_range', oldMin, oldMax);
1839
1840            return $this;
1841        },
1842        /*
1843         * This method highlights the range of the slider apart from the
1844         * position of the slider grips.
1845         * To work well, the slider must have background color set to
1846         * transparent in the CSS or not set.
1847         */
1848        'highlight_range' : function(rangeMin, rangeMax) {
1849            var $this = this;
1850            var settings = $this.data('settings');
1851
1852            if (typeof settings.highlight === "undefined") {
1853                $.error('you cannot call highlight_range if you haven\' specified the "highlight" parameter in construction!');
1854            }
1855
1856            // avoid empty string
1857            if (!rangeMin) { rangeMin = 0; }
1858            if (!rangeMax) { rangeMax = 0; }
1859
1860            // we need to map rangeMin and rangeMax into pixels.
1861            var leftPx = methods.value_to_px.call($this, rangeMin),
1862                rightPx = methods.value_to_px.call($this, rangeMax),
1863                barWidth = rightPx - leftPx + $this.data('left_grip_width');
1864
1865            // set position
1866            var $highlightPanel = $this.find(
1867                settings.highlight.panel_selector
1868            );
1869
1870            $highlightPanel.css('left', leftPx + "px");
1871            $highlightPanel.css('width', barWidth + "px");
1872
1873            // keep the latest highlighted range, because if set_range is called
1874            // we must be able to update the highlighting.
1875            $this.data('highlightedRangeMin', rangeMin);
1876            $this.data('highlightedRangeMax', rangeMax);
1877
1878            // now decide wether the handler should be highlight
1879            _methods.refresh_grips_style.call($this);
1880
1881            return $this;
1882        },
1883        /*
1884         * Sets the increment rounding for the slider, see input parameters section
1885         * for more information.
1886         */
1887        'set_rounding' : function (rounding) {
1888            var $this = this;
1889
1890            if (typeof rounding === 'string' && rounding.indexOf('{') > -1) {
1891                // probably a json string
1892                rounding = $.parseJSON(rounding);
1893            }
1894
1895            $this.data('rounding', rounding);
1896            
1897            // build an array of roundings and sort it by value to facilitate search
1898            // when the range is going to be set.
1899            var roundings_array = [];
1900            if (typeof rounding === 'object') {
1901                // initial object has the form { value : range }
1902                var rounding_value;
1903                for (rounding_value in rounding) { // skip_javascript_test
1904                    if (rounding.hasOwnProperty(rounding_value)) {
1905                        var rounding_range = rounding[rounding_value];
1906                        roundings_array.push({ 
1907                            'range' : rounding_range, 
1908                            'value' : rounding_value 
1909                        });
1910                    }
1911                }
1912
1913                // now sort it by rounding range
1914                roundings_array.sort(function (a, b) { return a.range - b.range; });
1915
1916                $this.data('rounding_ranges', roundings_array);
1917            }
1918            else {
1919                $this.removeData('rounding_ranges');
1920            }
1921
1922            return $this;
1923        },
1924        'get_rounding' : function () {
1925            var $this = this;
1926            return $this.data('rounding');
1927        },
1928        /*
1929         * This method rounds a given value to the closest integer defined
1930         * according to the rounding. Examples:
1931         * rounding: 10 v: 12.3    --> 10
1932         * rounding: 1 v: 12.3     --> 12
1933         * rounding: 10 v: 12.6    --> 13
1934         */
1935        'round_value_according_to_rounding' : function(v) {
1936            var $this = this;
1937            var rounding = _methods.get_rounding_for_value.call($this, v);
1938
1939            if (rounding > 0) {
1940                // We bring ourselves in a space of unitary roundings. You can
1941                // imagine now that sliders range between a certain minimum and 
1942                // maximum, and we always increase/decrease of one.
1943                var increment = v / rounding;
1944
1945                // This is the proposed value.
1946                var increment_int = parseInt(increment, 10);
1947
1948                // delta is a positive number between 0 and 1 that tells us how
1949                // close is the slider to integer + 1 (i.e., the next rounding).
1950                // 0 means the grip is exactly on integer
1951                // 1 means the grip is on integer + 1.
1952                var delta = increment - increment_int;
1953
1954                // now use delta to modify or not the current value.
1955                if (delta > 0.5) {
1956                    increment_int++; 
1957                }
1958
1959                // we now move the 
1960                var rounded = increment_int * rounding;
1961
1962                return rounded;
1963            }
1964            else {
1965                $.error('rounding must be > 0, got ' + rounding + ' instead');
1966            }
1967            return v;
1968        },
1969        /*
1970         * Utility function. Given a value within the range of the slider,
1971         * converts the value in pixels. If a value_to_pixel_mapping function
1972         * is defined it will be used, otherwise a linear mapping is used for
1973         * the conversion.
1974         */
1975        'value_to_px' : function (value) {
1976            var $this = this,
1977                value_to_pixel_mapping_func = $this.data('value_to_pixel_mapping');
1978
1979            // try using non-linear mapping if it's there...
1980            if (typeof value_to_pixel_mapping_func !== 'undefined') {
1981                return value_to_pixel_mapping_func(value); 
1982            }
1983
1984            // ... use linear mapping otherwise
1985            var w = _methods.getSliderWidthPx.call($this) - $this.data('left_grip_width');
1986            return _methods.rangemap_0_to_n.call($this, value, w);
1987        }
1988    };
1989
1990    var __name__ = 'nstSlider';
1991
1992    $.fn[__name__] = function(method) {
1993        /*
1994         * Just a router for method calls
1995         */
1996        if (methods[method]) {
1997            if (this.data('initialized') === true) {
1998                // call a method
1999                return methods[method].apply(this,
2000                    Array.prototype.slice.call(arguments, 1)
2001                );
2002            }
2003            else {
2004                throw new Error('method ' + method + ' called on an uninitialized instance of ' + __name__);
2005            }
2006        }
2007        else if (typeof method === 'object' || !method) {
2008            // call init, user passed the settings as parameters
2009            this.data('initialized', true);
2010            return methods.init.apply(this, arguments);
2011        }
2012        else {
2013            $.error('Cannot call method ' + method);
2014        }
2015    };
2016})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.