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.