PageSourceSearch

https://pavestone.com/lib/rzslider.js

js pavestone.com collected 2026-10-06 18:02:47 UTC 71,734 bytes, 2,189 lines download raw bytes

1/**
2 * Angular JS slider directive
3 *
4 * (c) Rafal Zajac <[email protected]>
5 * http://github.com/rzajac/angularjs-slider
6 *
7 * Licensed under the MIT license
8 */
9/*jslint unparam: true */
10/*global angular: false, console: false, define, module */
11(function(root, factory) {
12  'use strict';
13  /* istanbul ignore next */
14  if (typeof define === 'function' && define.amd) {
15    // AMD. Register as an anonymous module.
16    define(['angular'], factory);
17  } else if (typeof module === 'object' && module.exports) {
18    // Node. Does not work with strict CommonJS, but
19    // only CommonJS-like environments that support module.exports,
20    // like Node.
21    // to support bundler like browserify
22    module.exports = factory(require('angular'));
23  } else {
24    // Browser globals (root is window)
25    factory(root.angular);
26  }
27
28}(this, function(angular) {
29  'use strict';
30  var module = angular.module('rzModule', [])
31
32  .factory('RzSliderOptions', function() {
33    var defaultOptions = {
34      floor: 0,
35      ceil: null, //defaults to rz-slider-model
36      step: 1,
37      precision: 0,
38      minRange: null,
39      maxRange: null,
40      pushRange: false,
41      minLimit: null,
42      maxLimit: null,
43      id: null,
44      translate: null,
45      getLegend: null,
46      stepsArray: null,
47      bindIndexForStepsArray: false,
48      draggableRange: false,
49      draggableRangeOnly: false,
50      showSelectionBar: false,
51      showSelectionBarEnd: false,
52      showSelectionBarFromValue: null,
53      hidePointerLabels: false,
54      hideLimitLabels: false,
55      readOnly: false,
56      disabled: false,
57      interval: 350,
58      showTicks: false,
59      showTicksValues: false,
60      ticksTooltip: null,
61      ticksValuesTooltip: null,
62      vertical: false,
63      getSelectionBarColor: null,
64      getTickColor: null,
65      getPointerColor: null,
66      keyboardSupport: true,
67      scale: 1,
68      enforceStep: true,
69      enforceRange: false,
70      noSwitching: false,
71      onlyBindHandles: false,
72      onStart: null,
73      onChange: null,
74      onEnd: null,
75      rightToLeft: false,
76      boundPointerLabels: true,
77      mergeRangeLabelsIfSame: false,
78      customTemplateScope: null
79    };
80    var globalOptions = {};
81
82    var factory = {};
83    /**
84     * `options({})` allows global configuration of all sliders in the
85     * application.
86     *
87     *   var app = angular.module( 'App', ['rzModule'], function( RzSliderOptions ) {
88     *     // show ticks for all sliders
89     *     RzSliderOptions.options( { showTicks: true } );
90     *   });
91     */
92    factory.options = function(value) {
93      angular.extend(globalOptions, value);
94    };
95
96    factory.getOptions = function(options) {
97      return angular.extend({}, defaultOptions, globalOptions, options);
98    };
99
100    return factory;
101  })
102
103  .factory('rzThrottle', function($timeout) {
104    /**
105     * rzThrottle
106     *
107     * Taken from underscore project
108     *
109     * @param {Function} func
110     * @param {number} wait
111     * @param {ThrottleOptions} options
112     * @returns {Function}
113     */
114    return function(func, wait, options) {
115      'use strict';
116      /* istanbul ignore next */
117      var getTime = (Date.now || function() {
118        return new Date().getTime();
119      });
120      var context, args, result;
121      var timeout = null;
122      var previous = 0;
123      options = options || {};
124      var later = function() {
125        previous = getTime();
126        timeout = null;
127        result = func.apply(context, args);
128        context = args = null;
129      };
130      return function() {
131        var now = getTime();
132        var remaining = wait - (now - previous);
133        context = this;
134        args = arguments;
135        if (remaining <= 0) {
136          $timeout.cancel(timeout);
137          timeout = null;
138          previous = now;
139          result = func.apply(context, args);
140          context = args = null;
141        } else if (!timeout && options.trailing !== false) {
142          timeout = $timeout(later, remaining);
143        }
144        return result;
145      };
146    }
147  })
148
149  .factory('RzSlider', function($timeout, $document, $window, $compile, RzSliderOptions, rzThrottle) {
150    'use strict';
151
152    /**
153     * Slider
154     *
155     * @param {ngScope} scope            The AngularJS scope
156     * @param {Element} sliderElem The slider directive element wrapped in jqLite
157     * @constructor
158     */
159    var Slider = function(scope, sliderElem) {
160      /**
161       * The slider's scope
162       *
163       * @type {ngScope}
164       */
165      this.scope = scope;
166
167      /**
168       * The slider inner low value (linked to rzSliderModel)
169       * @type {number}
170       */
171      this.lowValue = 0;
172
173      /**
174       * The slider inner high value (linked to rzSliderHigh)
175       * @type {number}
176       */
177      this.highValue = 0;
178
179      /**
180       * Slider element wrapped in jqLite
181       *
182       * @type {jqLite}
183       */
184      this.sliderElem = sliderElem;
185
186      /**
187       * Slider type
188       *
189       * @type {boolean} Set to true for range slider
190       */
191      this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
192
193      /**
194       * Values recorded when first dragging the bar
195       *
196       * @type {Object}
197       */
198      this.dragging = {
199        active: false,
200        value: 0,
201        difference: 0,
202        offset: 0,
203        lowLimit: 0,
204        highLimit: 0
205      };
206
207      /**
208       * property that handle position (defaults to left for horizontal)
209       * @type {string}
210       */
211      this.positionProperty = 'left';
212
213      /**
214       * property that handle dimension (defaults to width for horizontal)
215       * @type {string}
216       */
217      this.dimensionProperty = 'width';
218
219      /**
220       * Half of the width or height of the slider handles
221       *
222       * @type {number}
223       */
224      this.handleHalfDim = 0;
225
226      /**
227       * Maximum position the slider handle can have
228       *
229       * @type {number}
230       */
231      this.maxPos = 0;
232
233      /**
234       * Precision
235       *
236       * @type {number}
237       */
238      this.precision = 0;
239
240      /**
241       * Step
242       *
243       * @type {number}
244       */
245      this.step = 1;
246
247      /**
248       * The name of the handle we are currently tracking
249       *
250       * @type {string}
251       */
252      this.tracking = '';
253
254      /**
255       * Minimum value (floor) of the model
256       *
257       * @type {number}
258       */
259      this.minValue = 0;
260
261      /**
262       * Maximum value (ceiling) of the model
263       *
264       * @type {number}
265       */
266      this.maxValue = 0;
267
268
269      /**
270       * The delta between min and max value
271       *
272       * @type {number}
273       */
274      this.valueRange = 0;
275
276
277      /**
278       * If showTicks/showTicksValues options are number.
279       * In this case, ticks values should be displayed below the slider.
280       * @type {boolean}
281       */
282      this.intermediateTicks = false;
283
284      /**
285       * Set to true if init method already executed
286       *
287       * @type {boolean}
288       */
289      this.initHasRun = false;
290
291      /**
292       * Used to call onStart on the first keydown event
293       *
294       * @type {boolean}
295       */
296      this.firstKeyDown = false;
297
298      /**
299       * Internal flag to prevent watchers to be called when the sliders value are modified internally.
300       * @type {boolean}
301       */
302      this.internalChange = false;
303
304      /**
305       * Internal flag to keep track of the visibility of combo label
306       * @type {boolean}
307       */
308      this.cmbLabelShown = false;
309
310      // Slider DOM elements wrapped in jqLite
311      this.fullBar = null; // The whole slider bar
312      this.selBar = null; // Highlight between two handles
313      this.minH = null; // Left slider handle
314      this.maxH = null; // Right slider handle
315      this.flrLab = null; // Floor label
316      this.ceilLab = null; // Ceiling label
317      this.minLab = null; // Label above the low value
318      this.maxLab = null; // Label above the high value
319      this.cmbLab = null; // Combined label
320      this.ticks = null; // The ticks
321
322      // Initialize slider
323      this.init();
324    };
325
326    // Add instance methods
327    Slider.prototype = {
328
329      /**
330       * Initialize slider
331       *
332       * @returns {undefined}
333       */
334      init: function() {
335        var thrLow, thrHigh,
336          self = this;
337
338        var calcDimFn = function() {
339          self.calcViewDimensions();
340        };
341
342        this.applyOptions();
343        this.syncLowValue();
344        if (this.range)
345          this.syncHighValue();
346        this.initElemHandles();
347        this.manageElementsStyle();
348        this.setDisabledState();
349        this.calcViewDimensions();
350        this.setMinAndMax();
351        this.addAccessibility();
352        this.updateCeilLab();
353        this.updateFloorLab();
354        this.initHandles();
355        this.manageEventsBindings();
356
357        // Recalculate slider view dimensions
358        this.scope.$on('reCalcViewDimensions', calcDimFn);
359
360        // Recalculate stuff if view port dimensions have changed
361        angular.element($window).on('resize', calcDimFn);
362
363        this.initHasRun = true;
364
365        // Watch for changes to the model
366        thrLow = rzThrottle(function() {
367          self.onLowHandleChange();
368        }, self.options.interval);
369
370        thrHigh = rzThrottle(function() {
371          self.onHighHandleChange();
372        }, self.options.interval);
373
374        this.scope.$on('rzSliderForceRender', function() {
375          self.resetLabelsValue();
376          thrLow();
377          if (self.range) {
378            thrHigh();
379          }
380          self.resetSlider();
381        });
382
383        // Watchers (order is important because in case of simultaneous change,
384        // watchers will be called in the same order)
385        this.scope.$watch('rzSliderOptions()', function(newValue, oldValue) {
386          if (newValue === oldValue)
387            return;
388          self.applyOptions(); // need to be called before synchronizing the values
389          self.syncLowValue();
390          if (self.range)
391            self.syncHighValue();
392          self.resetSlider();
393        }, true);
394
395        this.scope.$watch('rzSliderModel', function(newValue, oldValue) {
396          if (self.internalChange)
397            return;
398          if (newValue === oldValue)
399            return;
400          thrLow();
401        });
402
403        this.scope.$watch('rzSliderHigh', function(newValue, oldValue) {
404          if (self.internalChange)
405            return;
406          if (newValue === oldValue)
407            return;
408          if (newValue != null)
409            thrHigh();
410          if (self.range && newValue == null || !self.range && newValue != null) {
411            self.applyOptions();
412            self.resetSlider();
413          }
414        });
415
416        this.scope.$on('$destroy', function() {
417          self.unbindEvents();
418          angular.element($window).off('resize', calcDimFn);
419        });
420      },
421
422      findStepIndex: function(modelValue) {
423        var index = 0;
424        for (var i = 0; i < this.options.stepsArray.length; i++) {
425          var step = this.options.stepsArray[i];
426          if (step === modelValue) {
427            index = i;
428            break;
429          }
430          else if (angular.isObject(step) && step.value === modelValue) {
431            index = i;
432            break;
433          }
434        }
435        return index;
436      },
437
438      syncLowValue: function() {
439        if (this.options.stepsArray) {
440          if (!this.options.bindIndexForStepsArray)
441            this.lowValue = this.findStepIndex(this.scope.rzSliderModel);
442          else
443            this.lowValue = this.scope.rzSliderModel
444        }
445        else
446          this.lowValue = this.scope.rzSliderModel;
447      },
448
449      syncHighValue: function() {
450        if (this.options.stepsArray) {
451          if (!this.options.bindIndexForStepsArray)
452            this.highValue = this.findStepIndex(this.scope.rzSliderHigh);
453          else
454            this.highValue = this.scope.rzSliderHigh
455        }
456        else
457          this.highValue = this.scope.rzSliderHigh;
458      },
459
460      getStepValue: function(sliderValue) {
461        var step = this.options.stepsArray[sliderValue];
462        if (angular.isObject(step))
463          return step.value;
464        return step;
465      },
466
467      applyLowValue: function() {
468        if (this.options.stepsArray) {
469          if (!this.options.bindIndexForStepsArray)
470            this.scope.rzSliderModel = this.getStepValue(this.lowValue);
471          else
472            this.scope.rzSliderModel = this.lowValue
473        }
474        else
475          this.scope.rzSliderModel = this.lowValue;
476      },
477
478      applyHighValue: function() {
479        if (this.options.stepsArray) {
480          if (!this.options.bindIndexForStepsArray)
481            this.scope.rzSliderHigh = this.getStepValue(this.highValue);
482          else
483            this.scope.rzSliderHigh = this.highValue
484        }
485        else
486          this.scope.rzSliderHigh = this.highValue;
487      },
488
489      /*
490       * Reflow the slider when the low handle changes (called with throttle)
491       */
492      onLowHandleChange: function() {
493        this.syncLowValue();
494        if (this.range)
495          this.syncHighValue();
496        this.setMinAndMax();
497        this.updateLowHandle(this.valueToOffset(this.lowValue));
498        this.updateSelectionBar();
499        this.updateTicksScale();
500        this.updateAriaAttributes();
501        if (this.range) {
502          this.updateCmbLabel();
503        }
504      },
505
506      /*
507       * Reflow the slider when the high handle changes (called with throttle)
508       */
509      onHighHandleChange: function() {
510        this.syncLowValue();
511        this.syncHighValue();
512        this.setMinAndMax();
513        this.updateHighHandle(this.valueToOffset(this.highValue));
514        this.updateSelectionBar();
515        this.updateTicksScale();
516        this.updateCmbLabel();
517        this.updateAriaAttributes();
518      },
519
520      /**
521       * Read the user options and apply them to the slider model
522       */
523      applyOptions: function() {
524        var sliderOptions;
525        if (this.scope.rzSliderOptions)
526          sliderOptions = this.scope.rzSliderOptions();
527        else
528          sliderOptions = {};
529
530        this.options = RzSliderOptions.getOptions(sliderOptions);
531
532        if (this.options.step <= 0)
533          this.options.step = 1;
534
535        this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
536        this.options.draggableRange = this.range && this.options.draggableRange;
537        this.options.draggableRangeOnly = this.range && this.options.draggableRangeOnly;
538        if (this.options.draggableRangeOnly) {
539          this.options.draggableRange = true;
540        }
541
542        this.options.showTicks = this.options.showTicks || this.options.showTicksValues;
543        this.scope.showTicks = this.options.showTicks; //scope is used in the template
544        if (angular.isNumber(this.options.showTicks))
545          this.intermediateTicks = true;
546
547        this.options.showSelectionBar = this.options.showSelectionBar || this.options.showSelectionBarEnd
548          || this.options.showSelectionBarFromValue !== null;
549
550        if (this.options.stepsArray) {
551          this.parseStepsArray();
552        } else {
553          if (this.options.translate)
554            this.customTrFn = this.options.translate;
555          else
556            this.customTrFn = function(value) {
557              return String(value);
558            };
559
560          this.getLegend = this.options.getLegend;
561        }
562
563        if (this.options.vertical) {
564          this.positionProperty = 'bottom';
565          this.dimensionProperty = 'height';
566        }
567
568        if (this.options.customTemplateScope)
569          this.scope.custom = this.options.customTemplateScope;
570      },
571
572      parseStepsArray: function() {
573        this.options.floor = 0;
574        this.options.ceil = this.options.stepsArray.length - 1;
575        this.options.step = 1;
576
577        if (this.options.translate) {
578          this.customTrFn = this.options.translate;
579        }
580        else {
581          this.customTrFn = function(modelValue) {
582            if (this.options.bindIndexForStepsArray)
583              return this.getStepValue(modelValue)
584            return modelValue;
585          };
586        }
587
588        this.getLegend = function(index) {
589          var step = this.options.stepsArray[index];
590          if (angular.isObject(step))
591            return step.legend;
592          return null;
593        };
594      },
595
596      /**
597       * Resets slider
598       *
599       * @returns {undefined}
600       */
601      resetSlider: function() {
602        this.manageElementsStyle();
603        this.addAccessibility();
604        this.setMinAndMax();
605        this.updateCeilLab();
606        this.updateFloorLab();
607        this.unbindEvents();
608        this.manageEventsBindings();
609        this.setDisabledState();
610        this.calcViewDimensions();
611      },
612
613      /**
614       * Set the slider children to variables for easy access
615       *
616       * Run only once during initialization
617       *
618       * @returns {undefined}
619       */
620      initElemHandles: function() {
621        // Assign all slider elements to object properties for easy access
622        angular.forEach(this.sliderElem.children(), function(elem, index) {
623          var jElem = angular.element(elem);
624
625          switch (index) {
626            case 0:
627              this.fullBar = jElem;
628              break;
629            case 1:
630              this.selBar = jElem;
631              break;
632            case 2:
633              this.minH = jElem;
634              break;
635            case 3:
636              this.maxH = jElem;
637              break;
638            case 4:
639              this.flrLab = jElem;
640              break;
641            case 5:
642              this.ceilLab = jElem;
643              break;
644            case 6:
645              this.minLab = jElem;
646              break;
647            case 7:
648              this.maxLab = jElem;
649              break;
650            case 8:
651              this.cmbLab = jElem;
652              break;
653            case 9:
654              this.ticks = jElem;
655              break;
656          }
657
658        }, this);
659
660        // Initialize offset cache properties
661        this.selBar.rzsp = 0;
662        this.minH.rzsp = 0;
663        this.maxH.rzsp = 0;
664        this.flrLab.rzsp = 0;
665        this.ceilLab.rzsp = 0;
666        this.minLab.rzsp = 0;
667        this.maxLab.rzsp = 0;
668        this.cmbLab.rzsp = 0;
669      },
670
671      /**
672       * Update each elements style based on options
673       */
674      manageElementsStyle: function() {
675
676        if (!this.range)
677          this.maxH.css('display', 'none');
678        else
679          this.maxH.css('display', '');
680
681
682        this.alwaysHide(this.flrLab, this.options.showTicksValues || this.options.hideLimitLabels);
683        this.alwaysHide(this.ceilLab, this.options.showTicksValues || this.options.hideLimitLabels);
684
685        var hideLabelsForTicks = this.options.showTicksValues && !this.intermediateTicks;
686        this.alwaysHide(this.minLab, hideLabelsForTicks || this.options.hidePointerLabels);
687        this.alwaysHide(this.maxLab, hideLabelsForTicks || !this.range || this.options.hidePointerLabels);
688        this.alwaysHide(this.cmbLab, hideLabelsForTicks || !this.range || this.options.hidePointerLabels);
689        this.alwaysHide(this.selBar, !this.range && !this.options.showSelectionBar);
690
691        if (this.options.vertical)
692          this.sliderElem.addClass('rz-vertical');
693
694        if (this.options.draggableRange)
695          this.selBar.addClass('rz-draggable');
696        else
697          this.selBar.removeClass('rz-draggable');
698
699        if (this.intermediateTicks && this.options.showTicksValues)
700          this.ticks.addClass('rz-ticks-values-under');
701      },
702
703      alwaysHide: function(el, hide) {
704        el.rzAlwaysHide = hide;
705        if (hide)
706          this.hideEl(el);
707        else
708          this.showEl(el)
709      },
710
711      /**
712       * Manage the events bindings based on readOnly and disabled options
713       *
714       * @returns {undefined}
715       */
716      manageEventsBindings: function() {
717        if (this.options.disabled || this.options.readOnly)
718          this.unbindEvents();
719        else
720          this.bindEvents();
721      },
722
723      /**
724       * Set the disabled state based on rzSliderDisabled
725       *
726       * @returns {undefined}
727       */
728      setDisabledState: function() {
729        if (this.options.disabled) {
730          this.sliderElem.attr('disabled', 'disabled');
731        } else {
732          this.sliderElem.attr('disabled', null);
733        }
734      },
735
736      /**
737       * Reset label values
738       *
739       * @return {undefined}
740       */
741      resetLabelsValue: function() {
742        this.minLab.rzsv = undefined;
743        this.maxLab.rzsv = undefined;
744      },
745
746      /**
747       * Initialize slider handles positions and labels
748       *
749       * Run only once during initialization and every time view port 
749changes size
750       *
751       * @returns {undefined}
752       */
753      initHandles: function() {
754        this.updateLowHandle(this.valueToOffset(this.lowValue));
755
756        /*
757         the order here is important since the selection bar should be
758         updated after the high handle but before the combined label
759         */
760        if (this.range)
761          this.updateHighHandle(this.valueToOffset(this.highValue));
762        this.updateSelectionBar();
763        if (this.range)
764          this.updateCmbLabel();
765
766        this.updateTicksScale();
767      },
768
769      /**
770       * Translate value to human readable format
771       *
772       * @param {number|string} value
773       * @param {jqLite} label
774       * @param {String} which
775       * @param {boolean} [useCustomTr]
776       * @returns {undefined}
777       */
778      translateFn: function(value, label, which, useCustomTr) {
779        useCustomTr = useCustomTr === undefined ? true : useCustomTr;
780
781        var valStr = '',
782          getDimension = false,
783          noLabelInjection = label.hasClass('no-label-injection');
784
785        if (useCustomTr) {
786          if (this.options.stepsArray && !this.options.bindIndexForStepsArray)
787            value = this.getStepValue(value);
788          valStr = String(this.customTrFn(value, this.options.id, which));
789        }
790        else {
791          valStr = String(value)
792        }
793
794        if (label.rzsv === undefined || label.rzsv.length !== valStr.length || (label.rzsv.length > 0 && label.rzsd === 0)) {
795          getDimension = true;
796          label.rzsv = valStr;
797        }
798
799        if (!noLabelInjection) {
800          label.html(valStr);
801        }
802        ;
803
804        this.scope[which + 'Label'] = valStr;
805
806        // Update width only when length of the label have changed
807        if (getDimension) {
808          this.getDimension(label);
809        }
810      },
811
812      /**
813       * Set maximum and minimum values for the slider and ensure the model and high
814       * value match these limits
815       * @returns {undefined}
816       */
817      setMinAndMax: function() {
818
819        this.step = +this.options.step;
820        this.precision = +this.options.precision;
821
822        this.minValue = this.options.floor;
823
824        if (this.options.enforceStep) {
825          this.lowValue = this.roundStep(this.lowValue);
826          if (this.range)
827            this.highValue = this.roundStep(this.highValue);
828        }
829
830        if (this.options.ceil != null)
831          this.maxValue = this.options.ceil;
832        else
833          this.maxValue = this.options.ceil = this.range ? this.highValue : this.lowValue;
834
835        if (this.options.enforceRange) {
836          this.lowValue = this.sanitizeValue(this.lowValue);
837          if (this.range)
838            this.highValue = this.sanitizeValue(this.highValue);
839        }
840
841        this.applyLowValue();
842        if (this.range)
843          this.applyHighValue();
844
845        this.valueRange = this.maxValue - this.minValue;
846      },
847
848      /**
849       * Adds accessibility attributes
850       *
851       * Run only once during initialization
852       *
853       * @returns {undefined}
854       */
855      addAccessibility: function() {
856        this.minH.attr('role', 'slider');
857        this.updateAriaAttributes();
858        if (this.options.keyboardSupport && !(this.options.readOnly || this.options.disabled))
859          this.minH.attr('tabindex', '0');
860        else
861          this.minH.attr('tabindex', '');
862        if (this.options.vertical)
863          this.minH.attr('aria-orientation', 'vertical');
864
865        if (this.range) {
866          this.maxH.attr('role', 'slider');
867          if (this.options.keyboardSupport && !(this.options.readOnly || this.options.disabled))
868            this.maxH.attr('tabindex', '0');
869          else
870            this.maxH.attr('tabindex', '');
871          if (this.options.vertical)
872            this.maxH.attr('aria-orientation', 'vertical');
873        }
874      },
875
876      /**
877       * Updates aria attributes according to current values
878       */
879      updateAriaAttributes: function() {
880        this.minH.attr({
881          'aria-valuenow': this.scope.rzSliderModel,
882          'aria-valuetext': this.customTrFn(this.scope.rzSliderModel, this.options.id, 'model'),
883          'aria-valuemin': this.minValue,
884          'aria-valuemax': this.maxValue
885        });
886        if (this.range) {
887          this.maxH.attr({
888            'aria-valuenow': this.scope.rzSliderHigh,
889            'aria-valuetext': this.customTrFn(this.scope.rzSliderHigh, this.options.id, 'high'),
890            'aria-valuemin': this.minValue,
891            'aria-valuemax': this.maxValue
892          });
893        }
894      },
895
896      /**
897       * Calculate dimensions that are dependent on view port size
898       *
899       * Run once during initialization and every time view port 
899changes size.
900       *
901       * @returns {undefined}
902       */
903      calcViewDimensions: function() {
904        var handleWidth = this.getDimension(this.minH);
905
906        this.handleHalfDim = handleWidth / 2;
907        this.barDimension = this.getDimension(this.fullBar);
908
909        this.maxPos = this.barDimension - handleWidth;
910
911        this.getDimension(this.sliderElem);
912        this.sliderElem.rzsp = this.sliderElem[0].getBoundingClientRect()[this.positionProperty];
913
914        if (this.initHasRun) {
915          this.updateFloorLab();
916          this.updateCeilLab();
917          this.initHandles();
918        }
919      },
920
921      /**
922       * Update the ticks position
923       *
924       * @returns {undefined}
925       */
926      updateTicksScale: function() {
927        if (!this.options.showTicks) return;
928        var step = this.step;
929        if (this.intermediateTicks)
930          step = this.options.showTicks;
931        var ticksCount = Math.round((this.maxValue - this.minValue) / step) + 1;
932        this.scope.ticks = [];
933        for (var i = 0; i < ticksCount; i++) {
934          var value = this.roundStep(this.minValue + i * step);
935          var tick = {
936            selected: this.isTickSelected(value)
937          };
938          if (tick.selected && this.options.getSelectionBarColor) {
939            tick.style = {
940              'background-color': this.getSelectionBarColor()
941            };
942          }
943          if (!tick.selected && this.options.getTickColor) {
944            tick.style = {
945              'background-color': this.getTickColor(value)
946            }
947          }
948          if (this.options.ticksTooltip) {
949            tick.tooltip = this.options.ticksTooltip(value);
950            tick.tooltipPlacement = this.options.vertical ? 'right' : 'top';
951          }
952          if (this.options.showTicksValues) {
953            tick.value = this.getDisplayValue(value, 'tick-value');
954            if (this.options.ticksValuesTooltip) {
955              tick.valueTooltip = this.options.ticksValuesTooltip(value);
956              tick.valueTooltipPlacement = this.options.vertical ? 'right' : 'top';
957            }
958          }
959          if (this.getLegend) {
960            var legend = this.getLegend(value, this.options.id);
961            if (legend)
962              tick.legend = legend;
963          }
964          if (!this.options.rightToLeft) {
965            this.scope.ticks.push(tick);
966          } else {
967            this.scope.ticks.unshift(tick);
968          }
969        }
970      },
971
972      isTickSelected: function(value) {
973        if (!this.range) {
974          if (this.options.showSelectionBarFromValue !== null) {
975            var center = this.options.showSelectionBarFromValue;
976            if (this.lowValue > center && value >= center && value <= this.lowValue)
977              return true;
978            else if (this.lowValue < center && value <= center && value >= this.lowValue)
979              return true;
980          }
981          else if (this.options.showSelectionBarEnd) {
982            if (value >= this.lowValue)
983              return true;
984          }
985          else if (this.options.showSelectionBar && value <= this.lowValue)
986            return true;
987        }
988        if (this.range && value >= this.lowValue && value <= this.highValue)
989          return true;
990        return false;
991      },
992
993      /**
994       * Update position of the floor label
995       *
996       * @returns {undefined}
997       */
998      updateFloorLab: function() {
999        this.translateFn(this.minValue, this.flrLab, 'floor');
1000        this.getDimension(this.flrLab);
1001        var position = this.options.rightToLeft ? this.barDimension - this.flrLab.rzsd : 0;
1002        this.setPosition(this.flrLab, position);
1003      },
1004
1005      /**
1006       * Update position of the ceiling label
1007       *
1008       * @returns {undefined}
1009       */
1010      updateCeilLab: function() {
1011        this.translateFn(this.maxValue, this.ceilLab, 'ceil');
1012        this.getDimension(this.ceilLab);
1013        var position = this.options.rightToLeft ? 0 : this.barDimension - this.ceilLab.rzsd;
1014        this.setPosition(this.ceilLab, position);
1015      },
1016
1017      /**
1018       * Update slider handles and label positions
1019       *
1020       * @param {string} which
1021       * @param {number} newOffset
1022       */
1023      updateHandles: function(which, newOffset) {
1024        if (which === 'lowValue')
1025          this.updateLowHandle(newOffset);
1026        else
1027          this.updateHighHandle(newOffset);
1028
1029        this.updateSelectionBar();
1030        this.updateTicksScale();
1031        if (this.range)
1032          this.updateCmbLabel();
1033      },
1034
1035      /**
1036       * Helper function to work out the position for handle labels depending on RTL or not
1037       *
1038       * @param {string} labelName maxLab or minLab
1039       * @param newOffset
1040       *
1041       * @returns {number}
1042       */
1043      getHandleLabelPos: function(labelName, newOffset) {
1044        var labelRzsd = this[labelName].rzsd,
1045          nearHandlePos = newOffset - labelRzsd / 2 + this.handleHalfDim,
1046          endOfBarPos = this.barDimension - labelRzsd;
1047
1048        if (!this.options.boundPointerLabels)
1049          return nearHandlePos;
1050
1051        if (this.options.rightToLeft && labelName === 'minLab' || !this.options.rightToLeft && labelName === 'maxLab') {
1052          return Math.min(nearHandlePos, endOfBarPos);
1053        } else {
1054          return Math.min(Math.max(nearHandlePos, 0), endOfBarPos);
1055        }
1056      },
1057
1058      /**
1059       * Update low slider handle position and label
1060       *
1061       * @param {number} newOffset
1062       * @returns {undefined}
1063       */
1064      updateLowHandle: function(newOffset) {
1065        this.setPosition(this.minH, newOffset);
1066        this.translateFn(this.lowValue, this.minLab, 'model');
1067        this.setPosition(this.minLab, this.getHandleLabelPos('minLab', newOffset));
1068
1069        if (this.options.getPointerColor) {
1070          var pointercolor = this.getPointerColor('min');
1071          this.scope.minPointerStyle = {
1072            backgroundColor: pointercolor
1073          };
1074        }
1075
1076        this.shFloorCeil();
1077      },
1078
1079      /**
1080       * Update high slider handle position and label
1081       *
1082       * @param {number} newOffset
1083       * @returns {undefined}
1084       */
1085      updateHighHandle: function(newOffset) {
1086        this.setPosition(this.maxH, newOffset);
1087        this.translateFn(this.highValue, this.maxLab, 'high');
1088        this.setPosition(this.maxLab, this.getHandleLabelPos('maxLab', newOffset));
1089
1090        if (this.options.getPointerColor) {
1091          var pointercolor = this.getPointerColor('max');
1092          this.scope.maxPointerStyle = {
1093            backgroundColor: pointercolor
1094          };
1095        }
1096
1097        this.shFloorCeil();
1098      },
1099
1100      /**
1101       * Show/hide floor/ceiling label
1102       *
1103       * @returns {undefined}
1104       */
1105      shFloorCeil: function() {
1106        // Show based only on hideLimitLabels if pointer labels are hidden
1107        if (this.options.hidePointerLabels) {
1108          return;
1109        }
1110        var flHidden = false,
1111          clHidden = false,
1112          isRTL = this.options.rightToLeft,
1113          flrLabPos = this.flrLab.rzsp,
1114          flrLabDim = this.flrLab.rzsd,
1115          minLabPos = this.minLab.rzsp,
1116          minLabDim = this.minLab.rzsd,
1117          maxLabPos = this.maxLab.rzsp,
1118          maxLabDim = this.maxLab.rzsd,
1119          cmbLabPos = this.cmbLab.rzsp,
1120          cmbLabDim = this.cmbLab.rzsd,
1121          ceilLabPos = this.ceilLab.rzsp,
1122          halfHandle = this.handleHalfDim,
1123          isMinLabAtFloor = isRTL ? minLabPos + minLabDim >= flrLabPos - flrLabDim - 5 : minLabPos <= flrLabPos + flrLabDim + 5,
1124          isMinLabAtCeil = isRTL ? minLabPos - minLabDim <= ceilLabPos + halfHandle + 10 : minLabPos + minLabDim >= ceilLabPos - halfHandle - 10,
1125          isMaxLabAtFloor = isRTL ? maxLabPos >= flrLabPos - flrLabDim - halfHandle : maxLabPos <= flrLabPos + flrLabDim + halfHandle,
1126          isMaxLabAtCeil = isRTL ? maxLabPos - maxLabDim <= ceilLabPos + 10 : maxLabPos + maxLabDim >= ceilLabPos - 10,
1127          isCmbLabAtFloor = isRTL ? cmbLabPos >= flrLabPos - flrLabDim - halfHandle : cmbLabPos <= flrLabPos + flrLabDim + halfHandle,
1128          isCmbLabAtCeil = isRTL ? cmbLabPos - cmbLabDim <= ceilLabPos + 10 : cmbLabPos + cmbLabDim >= ceilLabPos - 10
1129
1130
1131        if (isMinLabAtFloor) {
1132          flHidden = true;
1133          this.hideEl(this.flrLab);
1134        } else {
1135          flHidden = false;
1136          this.showEl(this.flrLab);
1137        }
1138
1139        if (isMinLabAtCeil) {
1140          clHidden = true;
1141          this.hideEl(this.ceilLab);
1142        } else {
1143          clHidden = false;
1144          this.showEl(this.ceilLab);
1145        }
1146
1147        if (this.range) {
1148          var hideCeil = this.cmbLabelShown ? isCmbLabAtCeil : isMaxLabAtCeil;
1149          var hideFloor = this.cmbLabelShown ? isCmbLabAtFloor : isMinLabAtFloor;
1150
1151          if (hideCeil) {
1152            this.hideEl(this.ceilLab);
1153          } else if (!clHidden) {
1154            this.showEl(this.ceilLab);
1155          }
1156
1157          // Hide or show floor label
1158          if (hideFloor) {
1159            this.hideEl(this.flrLab);
1160          } else if (!flHidden) {
1161            this.showEl(this.flrLab);
1162          }
1163        }
1164      },
1165
1166      /**
1167       * Update slider selection bar, combined label and range label
1168       *
1169       * @returns {undefined}
1170       */
1171      updateSelectionBar: function() {
1172        var position = 0,
1173          dimension = 0,
1174          isSelectionBarFromRight = this.options.rightToLeft ? !this.options.showSelectionBarEnd : this.options.showSelectionBarEnd,
1175          positionForRange = this.options.rightToLeft ? this.maxH.rzsp + this.handleHalfDim : this.minH.rzsp + this.handleHalfDim;
1176
1177        if (this.range) {
1178          dimension = Math.abs(this.maxH.rzsp - this.minH.rzsp);
1179          position = positionForRange;
1180        }
1181        else {
1182          if (this.options.showSelectionBarFromValue !== null) {
1183            var center = this.options.showSelectionBarFromValue,
1184              centerPosition = this.valueToOffset(center),
1185              isModelGreaterThanCenter = this.options.rightToLeft ? this.lowValue <= center : this.lowValue > center;
1186            if (isModelGreaterThanCenter) {
1187              dimension = this.minH.rzsp - centerPosition;
1188              position = centerPosition + this.handleHalfDim;
1189            }
1190            else {
1191              dimension = centerPosition - this.minH.rzsp;
1192              position = this.minH.rzsp + this.handleHalfDim;
1193            }
1194          }
1195          else if (isSelectionBarFromRight) {
1196            dimension = Math.abs(this.maxPos - this.minH.rzsp) + this.handleHalfDim;
1197            position = this.minH.rzsp + this.handleHalfDim;
1198          } else {
1199            dimension = Math.abs(this.maxH.rzsp - this.minH.rzsp) + this.handleHalfDim;
1200            position = 0;
1201          }
1202        }
1203        this.setDimension(this.selBar, dimension);
1204        this.setPosition(this.selBar, position);
1205        if (this.options.getSelectionBarColor) {
1206          var color = this.getSelectionBarColor();
1207          this.scope.barStyle = {
1208            backgroundColor: color
1209          };
1210        }
1211      },
1212
1213      /**
1214       * Wrapper around the getSelectionBarColor of the user to pass to
1215       * correct parameters
1216       */
1217      getSelectionBarColor: function() {
1218        if (this.range)
1219          return this.options.getSelectionBarColor(this.scope.rzSliderModel, this.scope.rzSliderHigh);
1220        return this.options.getSelectionBarColor(this.scope.rzSliderModel);
1221      },
1222
1223      /**
1224       * Wrapper around the getPointerColor of the user to pass to
1225       * correct parameters
1226       */
1227      getPointerColor: function(pointerType) {
1228        if (pointerType === 'max') {
1229          return this.options.getPointerColor(this.scope.rzSliderHigh, pointerType);
1230        }
1231        return this.options.getPointerColor(this.scope.rzSliderModel, pointerType);
1232      },
1233
1234      /**
1235       * Wrapper around the getTickColor of the user to pass to
1236       * correct parameters
1237       */
1238      getTickColor: function(value) {
1239        return this.options.getTickColor(value);
1240      },
1241
1242      /**
1243       * Update combined label position and value
1244       *
1245       * @returns {undefined}
1246       */
1247      updateCmbLabel: function() {
1248        var isLabelOverlap = null;
1249        if (this.options.rightToLeft) {
1250          isLabelOverlap = this.minLab.rzsp - this.minLab.rzsd - 10 <= this.maxLab.rzsp;
1251        } else {
1252          isLabelOverlap = this.minLab.rzsp + this.minLab.rzsd + 10 >= this.maxLab.rzsp;
1253        }
1254
1255        if (isLabelOverlap) {
1256          var lowTr = this.getDisplayValue(this.lowValue, 'model'),
1257            highTr = this.getDisplayValue(this.highValue, 'high'),
1258            labelVal = '';
1259          if (this.options.mergeRangeLabelsIfSame && lowTr === highTr) {
1260            labelVal = lowTr;
1261          } else {
1262            labelVal = this.options.rightToLeft ? highTr + ' - ' + lowTr : lowTr + ' - ' + highTr;
1263          }
1264
1265          this.translateFn(labelVal, this.cmbLab, 'cmb', false);
1266          var pos = this.options.boundPointerLabels ? Math.min(
1267            Math.max(
1268              this.selBar.rzsp + this.selBar.rzsd / 2 - this.cmbLab.rzsd / 2,
1269              0
1270            ),
1271            this.barDimension - this.cmbLab.rzsd
1272          ) : this.selBar.rzsp + this.selBar.rzsd / 2 - this.cmbLab.rzsd / 2;
1273
1274          this.setPosition(this.cmbLab, pos);
1275          this.cmbLabelShown = true;
1276          this.hideEl(this.minLab);
1277          this.hideEl(this.maxLab);
1278          this.showEl(this.cmbLab);
1279        } else {
1280          this.cmbLabelShown = false;
1281          this.showEl(this.maxLab);
1282          this.showEl(this.minLab);
1283          this.hideEl(this.cmbLab);
1284        }
1285      },
1286
1287      /**
1288       * Return the translated value if a translate function is provided else the original value
1289       * @param value
1290       * @param which if it's min or max handle
1291       * @returns {*}
1292       */
1293      getDisplayValue: function(value, which) {
1294        if (this.options.stepsArray && !this.options.bindIndexForStepsArray) {
1295          value = this.getStepValue(value);
1296        }
1297        return this.customTrFn(value, this.options.id, which);
1298      },
1299
1300      /**
1301       * Round value to step and precision based on minValue
1302       *
1303       * @param {number} value
1304       * @param {number} customStep a custom step to override the defined step
1305       * @returns {number}
1306       */
1307      roundStep: function(value, customStep) {
1308        var step = customStep ? customStep : this.step,
1309          steppedDifference = parseFloat((value - this.minValue) / step).toPrecision(12);
1310        steppedDifference = Math.round(+steppedDifference) * step;
1311        var newValue = (this.minValue + steppedDifference).toFixed(this.precision);
1312        return +newValue;
1313      },
1314
1315      /**
1316       * Hide element
1317       *
1318       * @param element
1319       * @returns {jqLite} The jqLite wrapped DOM element
1320       */
1321      hideEl: function(element) {
1322        return element.css({
1323          visibility: 'hidden'
1324        });
1325      },
1326
1327      /**
1328       * Show element
1329       *
1330       * @param element The jqLite wrapped DOM element
1331       * @returns {jqLite} The jqLite
1332       */
1333      showEl: function(element) {
1334        if (!!element.rzAlwaysHide) {
1335          return element;
1336        }
1337
1338        return element.css({
1339          visibility: 'visible'
1340        });
1341      },
1342
1343      /**
1344       * Set element left/top offset depending on whether slider is horizontal or vertical
1345       *
1346       * @param {jqLite} elem The jqLite wrapped DOM element
1347       * @param {number} pos
1348       * @returns {number}
1349       */
1350      setPosition: function(elem, pos) {
1351        elem.rzsp = pos;
1352        var css = {};
1353        css[this.positionProperty] = pos + 'px';
1354        elem.css(css);
1355        return pos;
1356      },
1357
1358      /**
1359       * Get element width/height depending on whether slider is horizontal or vertical
1360       *
1361       * @param {jqLite} elem The jqLite wrapped DOM element
1362       * @returns {number}
1363       */
1364      getDimension: function(elem) {
1365        var val = elem[0].getBoundingClientRect();
1366        if (this.options.vertical)
1367          elem.rzsd = (val.bottom - val.top) * this.options.scale;
1368        else
1369          elem.rzsd = (val.right - val.left) * this.options.scale;
1370        return elem.rzsd;
1371      },
1372
1373      /**
1374       * Set element width/height depending on whether slider is horizontal or vertical
1375       *
1376       * @param {jqLite} elem  The jqLite wrapped DOM element
1377       * @param {number} dim
1378       * @returns {number}
1379       */
1380      setDimension: function(elem, dim) {
1381        elem.rzsd = dim;
1382        var css = {};
1383        css[this.dimensionProperty] = dim + 'px';
1384        elem.css(css);
1385        return dim;
1386      },
1387
1388      /**
1389       * Translate value to pixel offset
1390       *
1391       * @param {number} val
1392       * @returns {number}
1393       */
1394      valueToOffset: function(val) {
1395        if (this.options.rightToLeft) {
1396          return (this.maxValue - this.sanitizeValue(val)) * this.maxPos / this.valueRange || 0;
1397        }
1398        return (this.sanitizeValue(val) - this.minValue) * this.maxPos / this.valueRange || 0;
1399      },
1400
1401      /**
1402       * Returns a value that is within slider range
1403       *
1404       * @param {number} val
1405       * @returns {number}
1406       */
1407      sanitizeValue: function(val) {
1408        return Math.min(Math.max(val, this.minValue), this.maxValue);
1409      },
1410
1411      /**
1412       * Translate offset to model value
1413       *
1414       * @param {number} offset
1415       * @returns {number}
1416       */
1417      offsetToValue: function(offset) {
1418        if (this.options.rightToLeft) {
1419          return (1 - (offset / this.maxPos)) * this.valueRange + this.minValue;
1420        }
1421        return (offset / this.maxPos) * this.valueRange + this.minValue;
1422      },
1423
1424      // Events
1425
1426      /**
1427       * Get the X-coordinate or Y-coordinate of an event
1428       *
1429       * @param {Object} event  The event
1430       * @returns {number}
1431       */
1432      getEventXY: function(event) {
1433        /* http://stackoverflow.com/a/12336075/282882 */
1434        //noinspection JSLint
1435        var clientXY = this.options.vertical ? 'clientY' : 'clientX';
1436        if (event[clientXY] !== undefined) {
1437          return event[clientXY];
1438        }
1439
1440        return event.originalEvent === undefined ?
1441          event.touches[0][clientXY] : event.originalEvent.touches[0][clientXY];
1442      },
1443
1444      /**
1445       * Compute the event position depending on whether the slider is horizontal or vertical
1446       * @param event
1447       * @returns {number}
1448       */
1449      getEventPosition: function(event) {
1450        var sliderPos = this.sliderElem.rzsp,
1451          eventPos = 0;
1452        if (this.options.vertical)
1453          eventPos = -this.getEventXY(event) + sliderPos;
1454        else
1455          eventPos = this.getEventXY(event) - sliderPos;
1456        return (eventPos - this.handleHalfDim) * this.options.scale;
1457      },
1458
1459      /**
1460       * Get event names for move and event end
1461       *
1462       * @param {Event}    event    The event
1463       *
1464       * @return {{moveEvent: string, endEvent: string}}
1465       */
1466      getEventNames: function(event) {
1467        var eventNames = {
1468          moveEvent: '',
1469          endEvent: ''
1470        };
1471
1472        if (event.touches || (event.originalEvent !== undefined && event.originalEvent.touches)) {
1473          eventNames.moveEvent = 'touchmove';
1474          eventNames.endEvent = 'touchend';
1475        } else {
1476          eventNames.moveEvent = 'mousemove';
1477          eventNames.endEvent = 'mouseup';
1478        }
1479
1480        return eventNames;
1481      },
1482
1483      /**
1484       * Get the handle closest to an event.
1485       *
1486       * @param event {Event} The event
1487       * @returns {jqLite} The handle closest to the event.
1488       */
1489      getNearestHandle: function(event) {
1490        if (!this.range) {
1491          return this.minH;
1492        }
1493        var offset = this.getEventPosition(event),
1494          distanceMin = Math.abs(offset - this.minH.rzsp),
1495          distanceMax = Math.abs(offset - this.maxH.rzsp);
1496        if (distanceMin < distanceMax)
1497          return this.minH;
1498        else if (distanceMin > distanceMax)
1499          return this.maxH;
1500        else if (!this.options.rightToLeft)
1501        //if event is at the same distance from min/max then if it's at left of minH, we return minH else maxH
1502          return offset < this.minH.rzsp ? this.minH : this.maxH;
1503        else
1504        //reverse in rtl
1505          return offset > this.minH.rzsp ? this.minH : this.maxH;
1506      },
1507
1508      /**
1509       * Wrapper function to focus an angular element
1510       *
1511       * @param el {AngularElement} the element to focus
1512       */
1513      focusElement: function(el) {
1514        var DOM_ELEMENT = 0;
1515        el[DOM_ELEMENT].focus();
1516      },
1517
1518      /**
1519       * Bind mouse and touch events to slider handles
1520       *
1521       * @returns {undefined}
1522       */
1523      bindEvents: function() {
1524        var barTracking, barStart, barMove;
1525
1526        if (this.options.draggableRange) {
1527          barTracking = 'rzSliderDrag';
1528          barStart = this.onDragStart;
1529          barMove = this.onDragMove;
1530        } else {
1531          barTracking = 'lowValue';
1532          barStart = this.onStart;
1533          barMove = this.onMove;
1534        }
1535
1536        if (!this.options.onlyBindHandles) {
1537          this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking));
1538          this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar));
1539        }
1540
1541        if (this.options.draggableRangeOnly) {
1542          this.minH.on('mousedown', angular.bind(this, barStart, null, barTracking));
1543          this.maxH.on('mousedown', angular.bind(this, barStart, null, barTracking));
1544        } else {
1545          this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'lowValue'));
1546          if (this.range) {
1547            this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'highValue'));
1548          }
1549          if (!this.options.onlyBindHandles) {
1550            this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null));
1551            this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar));
1552            this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null));
1553            this.ticks.on('mousedown', angular.bind(this, this.onTickClick, this.ticks));
1554          }
1555        }
1556
1557        if (!this.options.onlyBindHandles) {
1558          this.selBar.on('touchstart', angular.bind(this, barStart, null, barTracking));
1559          this.selBar.on('touchstart', angular.bind(this, barMove, this.selBar));
1560        }
1561        if (this.options.draggableRangeOnly) {
1562          this.minH.on('touchstart', angular.bind(this, barStart, null, barTracking));
1563          this.maxH.on('touchstart', angular.bind(this, barStart, null, barTracking));
1564        } else {
1565          this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'lowValue'));
1566          if (this.range) {
1567            this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'highValue'));
1568          }
1569          if (!this.options.onlyBindHandles) {
1570            this.fullBar.on('touchstart', angular.bind(this, this.onStart, null, null));
1571            this.fullBar.on('touchstart', angular.bind(this, this.onMove, this.fullBar));
1572            this.ticks.on('touchstart', angular.bind(this, this.onStart, null, null));
1573            this.ticks.on('touchstart', angular.bind(this, this.onTickClick, this.ticks));
1574          }
1575        }
1576
1577        if (this.options.keyboardSupport) {
1578          this.minH.on('focus', angular.bind(this, this.onPointerFocus, this.minH, 'lowValue'));
1579          if (this.range) {
1580            this.maxH.on('focus', angular.bind(this, this.onPointerFocus, this.maxH, 'highValue'));
1581          }
1582        }
1583      },
1584
1585      /**
1586       * Unbind mouse and touch events to slider handles
1587       *
1588       * @returns {undefined}
1589       */
1590      unbindEvents: function() {
1591        this.minH.off();
1592        this.maxH.off();
1593        this.fullBar.off();
1594        this.selBar.off();
1595        this.ticks.off();
1596      },
1597
1598      /**
1599       * onStart event handler
1600       *
1601       * @param {?Object} pointer The jqLite wrapped DOM element; if null, the closest handle is used
1602       * @param {?string} ref     The name of the handle being changed; if null, the closest handle's value is modified
1603       * @param {Event}   event   The event
1604       * @returns {undefined}
1605       */
1606      onStart: function(pointer, ref, event) {
1607        var ehMove, ehEnd,
1608          eventNames = this.getEventNames(event);
1609
1610        event.stopPropagation();
1611        event.preventDefault();
1612
1613        // We have to do this in case the HTML where the sliders are on
1614        // have been animated into view.
1615        this.calcViewDimensions();
1616
1617        if (pointer) {
1618          this.tracking = ref;
1619        } else {
1620          pointer = this.getNearestHandle(event);
1621          this.tracking = pointer === this.minH ? 'lowValue' : 'highValue';
1622        }
1623
1624        pointer.addClass('rz-active');
1625
1626        if (this.options.keyboardSupport)
1627          this.focusElement(pointer);
1628
1629        ehMove = angular.bind(this, this.dragging.active ? this.onDragMove : this.onMove, pointer);
1630        ehEnd = angular.bind(this, this.onEnd, ehMove);
1631
1632        $document.on(eventNames.moveEvent, ehMove);
1633        $document.one(eventNames.endEvent, ehEnd);
1634        this.callOnStart();
1635      },
1636
1637      /**
1638       * onMove event handler
1639       *
1640       * @param {jqLite} pointer
1641       * @param {Event}  event The event
1642       * @param {boolean}  fromTick if the event occured on a tick or not
1643       * @returns {undefined}
1644       */
1645      onMove: function(pointer, event, fromTick) {
1646        var newOffset = this.getEventPosition(event),
1647          newValue,
1648          ceilValue = this.options.rightToLeft ? this.minValue : this.maxValue,
1649          flrValue = this.options.rightToLeft ? this.maxValue : this.minValue;
1650
1651        if (newOffset <= 0) {
1652          newValue = flrValue;
1653        } else if (newOffset >= this.maxPos) {
1654          newValue = ceilValue;
1655        } else {
1656          newValue = this.offsetToValue(newOffset);
1657          if (fromTick && angular.isNumber(this.options.showTicks))
1658            newValue = this.roundStep(newValue, this.options.showTicks);
1659          else
1660            newValue = this.roundStep(newValue);
1661        }
1662        this.positionTrackingHandle(newValue);
1663      },
1664
1665      /**
1666       * onEnd event handler
1667       *
1668       * @param {Event}    event    The event
1669       * @param {Function} ehMove   The the bound move event handler
1670       * @returns {undefined}
1671       */
1672      onEnd: function(ehMove, event) {
1673        var moveEventName = this.getEventNames(event).moveEvent;
1674
1675        if (!this.options.keyboardSupport) {
1676          this.minH.removeClass('rz-active');
1677          this.maxH.removeClass('rz-active');
1678          this.tracking = '';
1679        }
1680        this.dragging.active = false;
1681
1682        $document.off(moveEventName, ehMove);
1683        this.callOnEnd();
1684      },
1685
1686      onTickClick: function(pointer, event) {
1687        this.onMove(pointer, event, true);
1688      },
1689
1690      onPointerFocus: function(pointer, ref) {
1691        this.tracking = ref;
1692        pointer.one('blur', angular.bind(this, this.onPointerBlur, pointer));
1693        pointer.on('keydown', angular.bind(this, this.onKeyboardEvent));
1694        pointer.on('keyup', angular.bind(this, this.onKeyUp));
1695        this.firstKeyDown = true;
1696        pointer.addClass('rz-active');
1697      },
1698
1699      onKeyUp: function() {
1700        this.firstKeyDown = true;
1701        this.callOnEnd();
1702      },
1703
1704      onPointerBlur: function(pointer) {
1705        pointer.off('keydown');
1706        pointer.off('keyup');
1707        this.tracking = '';
1708        pointer.removeClass('rz-active');
1709      },
1710
1711      /**
1712       * Key actions helper function
1713       *
1714       * @param {number} currentValue value of the slider
1715       *
1716       * @returns {?Object} action value mappings
1717       */
1718      getKeyActions: function(currentValue) {
1719        var increaseStep = currentValue + this.step,
1720          decreaseStep = currentValue - this.step,
1721          increasePage = currentValue + this.valueRange / 10,
1722          decreasePage = currentValue - this.valueRange / 10;
1723
1724        //Left to right default actions
1725        var actions = {
1726          'UP': increaseStep,
1727          'DOWN': decreaseStep,
1728          'LEFT': decreaseStep,
1729          'RIGHT': increaseStep,
1730          'PAGEUP': increasePage,
1731          'PAGEDOWN': decreasePage,
1732          'HOME': this.minValue,
1733          'END': this.maxValue
1734        };
1735        //right to left means swapping right and left arrows
1736        if (this.options.rightToLeft) {
1737          actions.LEFT = increaseStep;
1738          actions.RIGHT = decreaseStep;
1739          // right to left and vertical means we also swap up and down
1740          if (this.options.vertical) {
1741            actions.UP = decreaseStep;
1742            actions.DOWN = increaseStep;
1743          }
1744        }
1745        return actions;
1746      },
1747
1748      onKeyboardEvent: function(event) {
1749        var currentValue = this[this.tracking],
1750          keyCode = event.keyCode || event.which,
1751          keys = {
1752            38: 'UP',
1753            40: 'DOWN',
1754            37: 'LEFT',
1755            39: 'RIGHT',
1756            33: 'PAGEUP',
1757            34: 'PAGEDOWN',
1758            36: 'HOME',
1759            35: 'END'
1760          },
1761          actions = this.getKeyActions(currentValue),
1762          key = keys[keyCode],
1763          action = actions[key];
1764        if (action == null || this.tracking === '') return;
1765        event.preventDefault();
1766
1767        if (this.firstKeyDown) {
1768          this.firstKeyDown = false;
1769          this.callOnStart();
1770        }
1771
1772        var self = this;
1773        $timeout(function() {
1774          var newValue = self.roundStep(self.sanitizeValue(action));
1775          if (!self.options.draggableRangeOnly) {
1776            self.positionTrackingHandle(newValue);
1777          }
1778          else {
1779            var difference = self.highValue - self.lowValue,
1780              newMinValue, newMaxValue;
1781            if (self.tracking === 'lowValue') {
1782              newMinValue = newValue;
1783              newMaxValue = newValue + difference;
1784              if (newMaxValue > self.maxValue) {
1785                newMaxValue = self.maxValue;
1786                newMinValue = newMaxValue - difference;
1787              }
1788            } else {
1789              newMaxValue = newValue;
1790              newMinValue = newValue - difference;
1791              if (newMinValue < self.minValue) {
1792                newMinValue = self.minValue;
1793                newMaxValue = newMinValue + difference;
1794              }
1795            }
1796            self.positionTrackingBar(newMinValue, newMaxValue);
1797          }
1798        });
1799      },
1800
1801      /**
1802       * onDragStart event handler
1803       *
1804       * Handles dragging of the middle bar.
1805       *
1806       * @param {Object} pointer The jqLite wrapped DOM element
1807       * @param {string} ref     One of the refLow, refHigh values
1808       * @param {Event}  event   The event
1809       * @returns {undefined}
1810       */
1811      onDragStart: function(pointer, ref, event) {
1812        var offset = this.getEventPosition(event);
1813        this.dragging = {
1814          active: true,
1815          value: this.offsetToValue(offset),
1816          difference: this.highValue - this.lowValue,
1817          lowLimit: this.options.rightToLeft ? this.minH.rzsp - offset : offset - this.minH.rzsp,
1818          highLimit: this.options.rightToLeft ? offset - this.maxH.rzsp : this.maxH.rzsp - offset
1819        };
1820
1821        this.onStart(pointer, ref, event);
1822      },
1823
1824      /**
1825       * getValue helper function
1826       *
1827       * gets max or min value depending on whether the newOffset is outOfBounds above or below the bar and rightToLeft
1828       *
1829       * @param {string} type 'max' || 'min' The value we are calculating
1830       * @param {number} newOffset  The new offset
1831       * @param {boolean} outOfBounds Is the new offset above or below the max/min?
1832       * @param {boolean} isAbove Is the new offset above the bar if out of bounds?
1833       *
1834       * @returns {number}
1835       */
1836      getValue: function(type, newOffset, outOfBounds, isAbove) {
1837        var isRTL = this.options.rightToLeft,
1838          value = null;
1839
1840        if (type === 'min') {
1841          if (outOfBounds) {
1842            if (isAbove) {
1843              value = isRTL ? this.minValue : this.maxValue - this.dragging.difference;
1844            } else {
1845              value = isRTL ? this.maxValue - this.dragging.difference : this.minValue;
1846            }
1847          } else {
1848            value = isRTL ? this.offsetToValue(newOffset + this.dragging.lowLimit) : this.offsetToValue(newOffset - this.dragging.lowLimit)
1849          }
1850        } else {
1851          if (outOfBounds) {
1852            if (isAbove) {
1853              value = isRTL ? this.minValue + this.dragging.difference : this.maxValue;
1854            } else {
1855              value = isRTL ? this.maxValue : this.minValue + this.dragging.difference;
1856            }
1857          } else {
1858            if (isRTL) {
1859              value = this.offsetToValue(newOffset + this.dragging.lowLimit) + this.dragging.difference
1860            } else {
1861              value = this.offsetToValue(newOffset - this.dragging.lowLimit) + this.dragging.difference;
1862            }
1863          }
1864        }
1865        return this.roundStep(value);
1866      },
1867
1868      /**
1869       * onDragMove event handler
1870       *
1871       * Handles dragging of the middle bar.
1872       *
1873       * @param {jqLite} pointer
1874       * @param {Event}  event The event
1875       * @returns {undefined}
1876       */
1877      onDragMove: function(pointer, event) {
1878        var newOffset = this.getEventPosition(event),
1879          newMinValue, newMaxValue,
1880          ceilLimit, flrLimit,
1881          isUnderFlrLimit, isOverCeilLimit,
1882          flrH, ceilH;
1883
1884        if (this.options.rightToLeft) {
1885          ceilLimit = this.dragging.lowLimit;
1886          flrLimit = this.dragging.highLimit;
1887          flrH = this.maxH;
1888          ceilH = this.minH;
1889        } else {
1890          ceilLimit = this.dragging.highLimit;
1891          flrLimit = this.dragging.lowLimit;
1892          flrH = this.minH;
1893          ceilH = this.maxH;
1894        }
1895        isUnderFlrLimit = newOffset <= flrLimit;
1896        isOverCeilLimit = newOffset >= this.maxPos - ceilLimit;
1897
1898        if (isUnderFlrLimit) {
1899          if (flrH.rzsp === 0)
1900            return;
1901          newMinValue = this.getValue('min', newOffset, true, false);
1902          newMaxValue = this.getValue('max', newOffset, true, false);
1903        } else if (isOverCeilLimit) {
1904          if (ceilH.rzsp === this.maxPos)
1905            return;
1906          newMaxValue = this.getValue('max', newOffset, true, true);
1907          newMinValue = this.getValue('min', newOffset, true, true);
1908        } else {
1909          newMinValue = this.getValue('min', newOffset, false);
1910          newMaxValue = this.getValue('max', newOffset, false);
1911        }
1912        this.positionTrackingBar(newMinValue, newMaxValue);
1913      },
1914
1915      /**
1916       * Set the new value and offset for the entire bar
1917       *
1918       * @param {number} newMinValue   the new minimum value
1919       * @param {number} newMaxValue   the new maximum value
1920       */
1921      positionTrackingBar: function(newMinValue, newMaxValue) {
1922
1923        if (this.options.minLimit != null && newMinValue < this.options.minLimit) {
1924          newMinValue = this.options.minLimit;
1925          newMaxValue = newMinValue + this.dragging.difference;
1926        }
1927        if (this.options.maxLimit != null && newMaxValue > this.options.maxLimit){
1928          newMaxValue = this.options.maxLimit;
1929          newMinValue = newMaxValue - this.dragging.difference;
1930        }
1931
1932        this.lowValue = newMinValue;
1933        this.highValue = newMaxValue;
1934        this.applyLowValue();
1935        if (this.range)
1936          this.applyHighValue();
1937        this.applyModel();
1938        this.updateHandles('lowValue', this.valueToOffset(newMinValue));
1939        this.updateHandles('highValue', this.valueToOffset(newMaxValue));
1940      },
1941
1942      /**
1943       * Set the new value and offset to the current tracking handle
1944       *
1945       * @param {number} newValue new model value
1946       */
1947      positionTrackingHandle: function(newValue) {
1948        var valueChanged = false;
1949
1950        newValue = this.applyMinMaxLimit(newValue);
1951        if (this.range) {
1952          if (this.options.pushRange) {
1953            newValue = this.applyPushRange(newValue);
1954            valueChanged = true;
1955          }
1956          else {
1957            if (this.options.noSwitching) {
1958              if (this.tracking === 'lowValue' && newValue > this.highValue)
1959                newValue = this.applyMinMaxRange(this.highValue);
1960              else if (this.tracking === 'highValue' && newValue < this.lowValue)
1961                newValue = this.applyMinMaxRange(this.lowValue);
1962            }
1963            newValue = this.applyMinMaxRange(newValue);
1964            /* This is to check if we need to switch the min and max handles */
1965            if (this.tracking === 'lowValue' && newValue > this.highValue) {
1966              this.lowValue = this.highValue;
1967              this.applyLowValue();
1968              this.updateHandles(this.tracking, this.maxH.rzsp);
1969              this.updateAriaAttributes();
1970              this.tracking = 'highValue';
1971              this.minH.removeClass('rz-active');
1972              this.maxH.addClass('rz-active');
1973              if (this.options.keyboardSupport)
1974                this.focusElement(this.maxH);
1975              valueChanged = true;
1976            }
1977            else if (this.tracking === 'highValue' && newValue < this.lowValue) {
1978              this.highValue = this.lowValue;
1979              this.applyHighValue();
1980              this.updateHandles(this.tracking, this.minH.rzsp);
1981              this.updateAriaAttributes();
1982              this.tracking = 'lowValue';
1983              this.maxH.removeClass('rz-active');
1984              this.minH.addClass('rz-active');
1985              if (this.options.keyboardSupport)
1986                this.focusElement(this.minH);
1987              valueChanged = true;
1988            }
1989          }
1990        }
1991
1992        if (this[this.tracking] !== newValue) {
1993          this[this.tracking] = newValue;
1994          if (this.tracking === 'lowValue')
1995            this.applyLowValue();
1996          else
1997            this.applyHighValue();
1998          this.updateHandles(this.tracking, this.valueToOffset(newValue));
1999          this.updateAriaAttributes();
2000          valueChanged = true;
2001        }
2002
2003        if (valueChanged)
2004          this.applyModel();
2005      },
2006
2007      applyMinMaxLimit: function(newValue) {
2008        if (this.options.minLimit != null && newValue < this.options.minLimit)
2009          return this.options.minLimit;
2010        if (this.options.maxLimit != null && newValue > this.options.maxLimit)
2011          return this.options.maxLimit;
2012        return newValue;
2013      },
2014
2015      applyMinMaxRange: function(newValue) {
2016        var oppositeValue = this.tracking === 'lowValue' ? this.highValue : this.lowValue,
2017          difference = Math.abs(newValue - oppositeValue);
2018        if (this.options.minRange != null) {
2019          if (difference < this.options.minRange) {
2020            if (this.tracking === 'lowValue')
2021              return this.highValue - this.options.minRange;
2022            else
2023              return this.lowValue + this.options.minRange;
2024          }
2025        }
2026        if (this.options.maxRange != null) {
2027          if (difference > this.options.maxRange) {
2028            if (this.tracking === 'lowValue')
2029              return this.highValue - this.options.maxRange;
2030            else
2031              return this.lowValue + this.options.maxRange;
2032          }
2033        }
2034        return newValue;
2035      },
2036
2037      applyPushRange: function(newValue) {
2038        var difference = this.tracking === 'lowValue' ? this.highValue - newValue : newValue - this.lowValue,
2039          range = this.options.minRange !== null ? this.options.minRange : this.options.step;
2040        if (difference < range) {
2041          if (this.tracking === 'lowValue') {
2042            this.highValue = Math.min(newValue + range, this.maxValue);
2043            newValue = this.highValue - range;
2044            this.applyHighValue();
2045            this.updateHandles('highValue', this.valueToOffset(this.highValue));
2046          }
2047          else {
2048            this.lowValue = Math.max(newValue - range, this.minValue);
2049            newValue = this.lowValue + range;
2050            this.applyLowValue();
2051            this.updateHandles('lowValue', this.valueToOffset(this.lowValue));
2052          }
2053          this.updateAriaAttributes();
2054        }
2055        return newValue;
2056      },
2057
2058      /**
2059       * Apply the model values using scope.$apply.
2060       * We wrap it with the internalChange flag to avoid the watchers to be called
2061       */
2062      applyModel: function() {
2063        this.internalChange = true;
2064        this.scope.$apply();
2065        this.callOnChange();
2066        this.internalChange = false;
2067      },
2068
2069      /**
2070       * Call the onStart callback if defined
2071       * The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
2072       *
2073       * @returns {undefined}
2074       */
2075      callOnStart: function() {
2076        if (this.options.onStart) {
2077          var self = this,
2078            pointerType = this.tracking === 'lowValue' ? 'min' : 'max';
2079          this.scope.$evalAsync(function() {
2080            self.options.onStart(self.options.id, self.scope.rzSliderModel, self.scope.rzSliderHigh, pointerType);
2081          });
2082        }
2083      },
2084
2085      /**
2086       * Call the onChange callback if defined
2087       * The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
2088       *
2089       * @returns {undefined}
2090       */
2091      callOnChange: function() {
2092        if (this.options.onChange) {
2093          var self = this,
2094            pointerType = this.tracking === 'lowValue' ? 'min' : 'max';
2095          this.scope.$evalAsync(function() {
2096            self.options.onChange(self.options.id, self.scope.rzSliderModel, self.scope.rzSliderHigh, pointerType);
2097          });
2098        }
2099      },
2100
2101      /**
2102       * Call the onEnd callback if defined
2103       * The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
2104       *
2105       * @returns {undefined}
2106       */
2107      callOnEnd: function() {
2108        if (this.options.onEnd) {
2109          var self = this,
2110            pointerType = this.tracking === 'lowValue' ? 'min' : 'max';
2111          this.scope.$evalAsync(function() {
2112            self.options.onEnd(self.options.id, self.scope.rzSliderModel, self.scope.rzSliderHigh, pointerType);
2113          });
2114        }
2115        this.scope.$emit('slideEnded');
2116      }
2117    };
2118
2119    return Slider;
2120  })
2121
2122  .directive('rzslider', function(RzSlider) {
2123    'use strict';
2124
2125    return {
2126      restrict: 'AE',
2127      replace: true,
2128      scope: {
2129        rzSliderModel: '=?',
2130        rzSliderHigh: '=?',
2131        rzSliderOptions: '&?',
2132        rzSliderTplUrl: '@'
2133      },
2134
2135      /**
2136       * Return template URL
2137       *
2138       * @param {jqLite} elem
2139       * @param {Object} attrs
2140       * @return {string}
2141       */
2142      templateUrl: function(elem, attrs) {
2143        //noinspection JSUnresolvedVariable
2144        return attrs.rzSliderTplUrl || 'rzSliderTpl.html';
2145      },
2146
2147      link: function(scope, elem) {
2148        scope.slider = new RzSlider(scope, elem);
2148 //attach on scope so we can test it
2149      }
2150    };
2151  });
2152
2153  // IDE assist
2154
2155  /**
2156   * @name ngScope
2157   *
2158   * @property {number} rzSliderModel
2159   * @property {number} rzSliderHigh
2160   * @property {Object} rzSliderOptions
2161   */
2162
2163  /**
2164   * @name jqLite
2165   *
2166   * @property {number|undefined} rzsp rzslider label position offset
2167   * @property {number|undefined} rzsd rzslider element dimension
2168   * @property {string|undefined} rzsv rzslider label value/text
2169   * @property {Function} css
2170   * @property {Function} text
2171   */
2172
2173  /**
2174   * @name Event
2175   * @property {Array} touches
2176   * @property {Event} originalEvent
2177   */
2178
2179  /**
2180   * @name ThrottleOptions
2181   *
2182   * @property {boolean} leading
2183   * @property {boolean} trailing
2184   */
2185
2186  /*templateReplacement*/
2187
2188  return module.name
2189}));

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.