UNPKG

angularjs-slider

Version:

AngularJS slider directive with no external dependencies. Mobile friendly!.

1,563 lines (1,421 loc) 92 kB
/*! angularjs-slider - v7.0.1 - (c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervi.eu>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com> - https://github.com/angular-slider/angularjs-slider - 2021-09-07 */ /*jslint unparam: true */ /*global angular: false, console: false, define, module */ ;(function(root, factory) { 'use strict' /* istanbul ignore next */ if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['angular'], factory) } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. // to support bundler like browserify var angularObj = angular || require('angular') if ((!angularObj || !angularObj.module) && typeof angular != 'undefined') { angularObj = angular } module.exports = factory(angularObj) } else { // Browser globals (root is window) factory(root.angular) } })(this, function(angular) { 'use strict' var module = angular .module('rzSlider', []) .factory('RzSliderOptions', function() { var defaultOptions = { floor: 0, ceil: null, //defaults to rz-slider-model step: 1, precision: 0, minRange: null, maxRange: null, restrictedRange: null, pushRange: false, minLimit: null, maxLimit: null, id: null, translate: null, getLegend: null, stepsArray: null, bindIndexForStepsArray: false, draggableRange: false, draggableRangeOnly: false, showSelectionBar: false, showSelectionBarEnd: false, showSelectionBarFromValue: null, showOuterSelectionBars: false, hidePointerLabels: false, hideLimitLabels: false, autoHideLimitLabels: true, readOnly: false, disabled: false, interval: 350, showTicks: false, showTicksValues: false, ticksArray: null, ticksTooltip: null, ticksValuesTooltip: null, vertical: false, getSelectionBarColor: null, getTickColor: null, getPointerColor: null, keyboardSupport: true, scale: 1, enforceStep: true, enforceRange: false, noSwitching: false, onlyBindHandles: false, disableAnimation: false, onStart: null, onChange: null, onEnd: null, rightToLeft: false, reversedControls: false, boundPointerLabels: true, mergeRangeLabelsIfSame: false, labelOverlapSeparator: ' - ', customTemplateScope: null, logScale: false, customValueToPosition: null, customPositionToValue: null, selectionBarGradient: null, ariaLabel: null, ariaLabelledBy: null, ariaLabelHigh: null, ariaLabelledByHigh: null, } var globalOptions = {} var factory = {} /** * `options({})` allows global configuration of all sliders in the * application. * * var app = angular.module( 'App', ['rzSlider'], function( RzSliderOptions ) { * // show ticks for all sliders * RzSliderOptions.options( { showTicks: true } ); * }); */ factory.options = function(value) { angular.extend(globalOptions, value) } factory.getOptions = function(options) { return angular.extend({}, defaultOptions, globalOptions, options) } return factory }) .factory('rzThrottle', ['$timeout', function($timeout) { /** * rzThrottle * * Taken from underscore project * * @param {Function} func * @param {number} wait * @param {ThrottleOptions} options * @returns {Function} */ return function(func, wait, options) { 'use strict' /* istanbul ignore next */ var getTime = Date.now || function() { return new Date().getTime() } var context, args, result var timeout = null var previous = 0 options = options || {} var later = function() { previous = getTime() timeout = null result = func.apply(context, args) context = args = null } return function() { var now = getTime() var remaining = wait - (now - previous) context = this args = arguments if (remaining <= 0) { $timeout.cancel(timeout) timeout = null previous = now result = func.apply(context, args) context = args = null } else if (!timeout && options.trailing !== false) { timeout = $timeout(later, remaining) } return result } } }]) .factory('RzSlider', ['$timeout', '$document', '$window', '$compile', 'RzSliderOptions', 'rzThrottle', function( $timeout, $document, $window, $compile, RzSliderOptions, rzThrottle ) { 'use strict' /** * Slider * * @param {ngScope} scope The AngularJS scope * @param {Element} sliderElem The slider directive element wrapped in jqLite * @constructor */ var Slider = function(scope, sliderElem) { /** * The slider's scope * * @type {ngScope} */ this.scope = scope /** * The slider inner low value (linked to rzSliderModel) * @type {number} */ this.lowValue = 0 /** * The slider inner high value (linked to rzSliderHigh) * @type {number} */ this.highValue = 0 /** * Slider element wrapped in jqLite * * @type {jqLite} */ this.sliderElem = sliderElem /** * Slider type * * @type {boolean} Set to true for range slider */ this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined /** * Values recorded when first dragging the bar * * @type {Object} */ this.dragging = { active: false, value: 0, difference: 0, position: 0, lowLimit: 0, highLimit: 0, } /** * property that handle position (defaults to left for horizontal) * @type {string} */ this.positionProperty = 'left' /** * property that handle dimension (defaults to width for horizontal) * @type {string} */ this.dimensionProperty = 'width' /** * Half of the width or height of the slider handles * * @type {number} */ this.handleHalfDim = 0 /** * Maximum position the slider handle can have * * @type {number} */ this.maxPos = 0 /** * Precision * * @type {number} */ this.precision = 0 /** * Step * * @type {number} */ this.step = 1 /** * The name of the handle we are currently tracking * * @type {string} */ this.tracking = '' /** * Minimum value (floor) of the model * * @type {number} */ this.minValue = 0 /** * Maximum value (ceiling) of the model * * @type {number} */ this.maxValue = 0 /** * The delta between min and max value * * @type {number} */ this.valueRange = 0 /** * If showTicks/showTicksValues options are number. * In this case, ticks values should be displayed below the slider. * @type {boolean} */ this.intermediateTicks = false /** * Set to true if init method already executed * * @type {boolean} */ this.initHasRun = false /** * Used to call onStart on the first keydown event * * @type {boolean} */ this.firstKeyDown = false /** * Internal flag to prevent watchers to be called when the sliders value are modified internally. * @type {boolean} */ this.internalChange = false /** * Internal flag to keep track of the visibility of combo label * @type {boolean} */ this.cmbLabelShown = false /** * Internal variable to keep track of the focus element */ this.currentFocusElement = null /** * Internal variable to know if we are already moving */ this.moving = false // Slider DOM elements wrapped in jqLite this.fullBar = null // The whole slider bar this.selBar = null // Highlight between two handles this.minH = null // Left slider handle this.maxH = null // Right slider handle this.flrLab = null // Floor label this.ceilLab = null // Ceiling label this.minLab = null // Label above the low value this.maxLab = null // Label above the high value this.cmbLab = null // Combined label this.ticks = null // The ticks // Initialize slider this.init() } // Add instance methods Slider.prototype = { /** * Initialize slider * * @returns {undefined} */ init: function() { var thrLow, thrHigh, self = this var calcDimFn = function() { self.calcViewDimensions() } this.applyOptions() this.syncLowValue() if (this.range) this.syncHighValue() this.initElemHandles() this.manageElementsStyle() this.setDisabledState() this.calcViewDimensions() this.setMinAndMax() this.updateRestrictionBar() this.addAccessibility() this.updateCeilLab() this.updateFloorLab() this.initHandles() this.manageEventsBindings() // Recalculate slider view dimensions this.scope.$on('reCalcViewDimensions', calcDimFn) // Recalculate stuff if view port dimensions have changed angular.element($window).on('resize', calcDimFn) this.initHasRun = true if (this.options.disableAnimation) { this.sliderElem.addClass('noanimate') } // Watch for changes to the model thrLow = rzThrottle(function() { self.onLowHandleChange() }, self.options.interval) thrHigh = rzThrottle(function() { self.onHighHandleChange() }, self.options.interval) this.scope.$on('rzSliderForceRender', function() { self.resetLabelsValue() thrLow() if (self.range) { thrHigh() } self.resetSlider() }) // Watchers (order is important because in case of simultaneous change, // watchers will be called in the same order) this.scope.$watchCollection('rzSliderOptions()', function( newValue, oldValue ) { if (newValue === oldValue) return self.applyOptions() // need to be called before synchronizing the values self.syncLowValue() if (self.range) self.syncHighValue() self.resetSlider() }) this.scope.$watch('rzSliderModel', function(newValue, oldValue) { if (self.internalChange) return if (newValue === oldValue) return thrLow() }) this.scope.$watch('rzSliderHigh', function(newValue, oldValue) { if (self.internalChange) return if (newValue === oldValue) return if (newValue != null) thrHigh() if ( (self.range && newValue == null) || (!self.range && newValue != null) ) { self.applyOptions() self.resetSlider() } }) this.scope.$on('$destroy', function() { self.unbindEvents() angular.element($window).off('resize', calcDimFn) self.currentFocusElement = null }) }, findStepIndex: function(modelValue) { var index = 0 for (var i = 0; i < this.options.stepsArray.length; i++) { var step = this.options.stepsArray[i] if (step === modelValue) { index = i break } else if (angular.isDate(step)) { if (step.getTime() === modelValue.getTime()) { index = i break } } else if (angular.isObject(step)) { if ( (angular.isDate(step.value) && step.value.getTime() === modelValue.getTime()) || step.value === modelValue ) { index = i break } } } return index }, syncLowValue: function() { if (this.options.stepsArray) { if (!this.options.bindIndexForStepsArray) this.lowValue = this.findStepIndex(this.scope.rzSliderModel) else this.lowValue = this.scope.rzSliderModel } else this.lowValue = this.scope.rzSliderModel }, syncHighValue: function() { if (this.options.stepsArray) { if (!this.options.bindIndexForStepsArray) this.highValue = this.findStepIndex(this.scope.rzSliderHigh) else this.highValue = this.scope.rzSliderHigh } else this.highValue = this.scope.rzSliderHigh }, getStepValue: function(sliderValue) { var step = this.options.stepsArray[sliderValue] if (angular.isDate(step)) return step if (angular.isObject(step)) return step.value return step }, applyLowValue: function() { if (this.options.stepsArray) { if (!this.options.bindIndexForStepsArray) this.scope.rzSliderModel = this.getStepValue(this.lowValue) else this.scope.rzSliderModel = this.lowValue } else this.scope.rzSliderModel = this.lowValue }, applyHighValue: function() { if (this.options.stepsArray) { if (!this.options.bindIndexForStepsArray) this.scope.rzSliderHigh = this.getStepValue(this.highValue) else this.scope.rzSliderHigh = this.highValue } else this.scope.rzSliderHigh = this.highValue }, /* * Reflow the slider when the low handle changes (called with throttle) */ onLowHandleChange: function() { this.syncLowValue() if (this.range) this.syncHighValue() this.setMinAndMax() this.updateLowHandle(this.valueToPosition(this.lowValue)) this.updateSelectionBar() this.updateTicksScale() this.updateAriaAttributes() if (this.range) { this.updateCmbLabel() } }, /* * Reflow the slider when the high handle changes (called with throttle) */ onHighHandleChange: function() { this.syncLowValue() this.syncHighValue() this.setMinAndMax() this.updateHighHandle(this.valueToPosition(this.highValue)) this.updateSelectionBar() this.updateTicksScale() this.updateCmbLabel() this.updateAriaAttributes() }, /** * Read the user options and apply them to the slider model */ applyOptions: function() { var sliderOptions if (this.scope.rzSliderOptions) sliderOptions = this.scope.rzSliderOptions() else sliderOptions = {} this.options = RzSliderOptions.getOptions(sliderOptions) if (this.options.step <= 0) this.options.step = 1 this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined this.options.draggableRange = this.range && this.options.draggableRange this.options.draggableRangeOnly = this.range && this.options.draggableRangeOnly if (this.options.draggableRangeOnly) { this.options.draggableRange = true } this.options.showTicks = this.options.showTicks || this.options.showTicksValues || !!this.options.ticksArray this.scope.showTicks = this.options.showTicks //scope is used in the template if ( angular.isNumber(this.options.showTicks) || this.options.ticksArray ) this.intermediateTicks = true this.options.showSelectionBar = this.options.showSelectionBar || this.options.showSelectionBarEnd || this.options.showSelectionBarFromValue !== null if (this.options.stepsArray) { this.parseStepsArray() } else { if (this.options.translate) this.customTrFn = this.options.translate else this.customTrFn = function(value) { return String(value) } this.getLegend = this.options.getLegend } if (this.options.vertical) { this.positionProperty = 'bottom' this.dimensionProperty = 'height' } else { this.positionProperty = 'left' this.dimensionProperty = 'width' } if (this.options.customTemplateScope) this.scope.custom = this.options.customTemplateScope }, parseStepsArray: function() { this.options.floor = 0 this.options.ceil = this.options.stepsArray.length - 1 this.options.step = 1 if (this.options.translate) { this.customTrFn = this.options.translate } else { this.customTrFn = function(modelValue) { if (this.options.bindIndexForStepsArray) return this.getStepValue(modelValue) return modelValue } } this.getLegend = function(index) { var step = this.options.stepsArray[index] if (angular.isObject(step)) return step.legend return null } }, /** * Resets slider * * @returns {undefined} */ resetSlider: function() { this.resetLabelsValue() this.manageElementsStyle() this.addAccessibility() this.setMinAndMax() this.updateCeilLab() this.updateFloorLab() this.unbindEvents() this.manageEventsBindings() this.setDisabledState() this.calcViewDimensions() this.updateRestrictionBar() this.refocusPointerIfNeeded() }, refocusPointerIfNeeded: function() { if (this.currentFocusElement) { this.onPointerFocus( this.currentFocusElement.pointer, this.currentFocusElement.ref ) this.focusElement(this.currentFocusElement.pointer) } }, /** * Set the slider children to variables for easy access * * Run only once during initialization * * @returns {undefined} */ initElemHandles: function() { // Assign all slider elements to object properties for easy access angular.forEach( this.sliderElem.children(), function(elem, index) { var jElem = angular.element(elem) switch (index) { case 0: this.leftOutSelBar = jElem break case 1: this.rightOutSelBar = jElem break case 2: this.fullBar = jElem break case 3: this.selBar = jElem break case 4: this.restrictedBar = jElem break case 5: this.minH = jElem break case 6: this.maxH = jElem break case 7: this.flrLab = jElem break case 8: this.ceilLab = jElem break case 9: this.minLab = jElem break case 10: this.maxLab = jElem break case 11: this.cmbLab = jElem break case 12: this.ticks = jElem break } }, this ) // Initialize position cache properties this.selBar.rzsp = 0 this.minH.rzsp = 0 this.maxH.rzsp = 0 this.flrLab.rzsp = 0 this.ceilLab.rzsp = 0 this.minLab.rzsp = 0 this.maxLab.rzsp = 0 this.cmbLab.rzsp = 0 }, /** * Update each elements style based on options */ manageElementsStyle: function() { if (!this.range) this.maxH.css('display', 'none') else this.maxH.css('display', '') this.alwaysHide( this.flrLab, this.options.showTicksValues || this.options.hideLimitLabels ) this.alwaysHide( this.ceilLab, this.options.showTicksValues || this.options.hideLimitLabels ) var hideLabelsForTicks = this.options.showTicksValues && !this.intermediateTicks this.alwaysHide( this.minLab, hideLabelsForTicks || this.options.hidePointerLabels ) this.alwaysHide( this.maxLab, hideLabelsForTicks || !this.range || this.options.hidePointerLabels ) this.alwaysHide( this.cmbLab, hideLabelsForTicks || !this.range || this.options.hidePointerLabels ) this.alwaysHide( this.selBar, !this.range && !this.options.showSelectionBar ) this.alwaysHide( this.leftOutSelBar, !this.range || !this.options.showOuterSelectionBars ) this.alwaysHide(this.restrictedBar, !this.options.restrictedRange) this.alwaysHide( this.rightOutSelBar, !this.range || !this.options.showOuterSelectionBars ) if (this.range && this.options.showOuterSelectionBars) { this.fullBar.addClass('rz-transparent') } if (this.options.vertical) { this.sliderElem.addClass('rz-vertical') } else { this.sliderElem.removeClass('rz-vertical') } if (this.options.draggableRange) this.selBar.addClass('rz-draggable') else this.selBar.removeClass('rz-draggable') if (this.intermediateTicks && this.options.showTicksValues) this.ticks.addClass('rz-ticks-values-under') }, alwaysHide: function(el, hide) { el.rzAlwaysHide = hide if (hide) this.hideEl(el) else this.showEl(el) }, /** * Manage the events bindings based on readOnly and disabled options * * @returns {undefined} */ manageEventsBindings: function() { if (this.options.disabled || this.options.readOnly) this.unbindEvents() else this.bindEvents() }, /** * Set the disabled state based on rzSliderDisabled * * @returns {undefined} */ setDisabledState: function() { if (this.options.disabled) { this.sliderElem.attr('disabled', 'disabled') } else { this.sliderElem.attr('disabled', null) } }, /** * Reset label values * * @return {undefined} */ resetLabelsValue: function() { this.minLab.rzsv = undefined this.maxLab.rzsv = undefined this.flrLab.rzsv = undefined this.ceilLab.rzsv = undefined this.cmbLab.rzsv = undefined this.resetPosition(this.flrLab) this.resetPosition(this.ceilLab) this.resetPosition(this.cmbLab) this.resetPosition(this.minLab) this.resetPosition(this.maxLab) }, /** * Initialize slider handles positions and labels * * Run only once during initialization and every time view port changes size * * @returns {undefined} */ initHandles: function() { this.updateLowHandle(this.valueToPosition(this.lowValue)) /* the order here is important since the selection bar should be updated after the high handle but before the combined label */ if (this.range) this.updateHighHandle(this.valueToPosition(this.highValue)) this.updateSelectionBar() if (this.range) this.updateCmbLabel() this.updateTicksScale() }, /** * Translate value to human readable format * * @param {number|string} value * @param {jqLite} label * @param {String} which * @param {boolean} [useCustomTr] * @returns {undefined} */ translateFn: function(value, label, which, useCustomTr) { useCustomTr = useCustomTr === undefined ? true : useCustomTr var valStr = '', getDimension = false, noLabelInjection = label.hasClass('no-label-injection') if (useCustomTr) { if (this.options.stepsArray && !this.options.bindIndexForStepsArray) value = this.getStepValue(value) valStr = String(this.customTrFn(value, this.options.id, which)) } else { valStr = String(value) } if ( label.rzsv === undefined || label.rzsv.length !== valStr.length || (label.rzsv.length > 0 && label.rzsd === 0) ) { getDimension = true label.rzsv = valStr } if (!noLabelInjection) { label.html(valStr) } this.scope[which + 'Label'] = valStr // Update width only when length of the label have changed if (getDimension) { this.getDimension(label) } }, /** * Set maximum and minimum values for the slider and ensure the model and high * value match these limits * @returns {undefined} */ setMinAndMax: function() { this.step = +this.options.step this.precision = +this.options.precision this.minValue = this.options.floor if (this.options.logScale && this.minValue === 0) throw Error("Can't use floor=0 with logarithmic scale") if (this.options.enforceStep) { this.lowValue = this.roundStep(this.lowValue) if (this.range) this.highValue = this.roundStep(this.highValue) } if (this.options.ceil != null) this.maxValue = this.options.ceil else this.maxValue = this.options.ceil = this.range ? this.highValue : this.lowValue if (this.options.enforceRange) { this.lowValue = this.sanitizeValue(this.lowValue) if (this.range) this.highValue = this.sanitizeValue(this.highValue) } this.applyLowValue() if (this.range) this.applyHighValue() this.valueRange = this.maxValue - this.minValue }, /** * Adds accessibility attributes * * Run only once during initialization * * @returns {undefined} */ addAccessibility: function() { this.minH.attr('role', 'slider') this.updateAriaAttributes() if ( this.options.keyboardSupport && !(this.options.readOnly || this.options.disabled) ) this.minH.attr('tabindex', '0') else this.minH.attr('tabindex', '') if (this.options.vertical) { this.minH.attr('aria-orientation', 'vertical') } else { this.minH.attr('aria-orientation', 'horizontal') } if (this.options.ariaLabel) this.minH.attr('aria-label', this.options.ariaLabel) else if (this.options.ariaLabelledBy) this.minH.attr('aria-labelledby', this.options.ariaLabelledBy) if (this.range) { this.maxH.attr('role', 'slider') if ( this.options.keyboardSupport && !(this.options.readOnly || this.options.disabled) ) this.maxH.attr('tabindex', '0') else this.maxH.attr('tabindex', '') if (this.options.vertical) this.maxH.attr('aria-orientation', 'vertical') else this.maxH.attr('aria-orientation', 'horizontal') if (this.options.ariaLabelHigh) this.maxH.attr('aria-label', this.options.ariaLabelHigh) else if (this.options.ariaLabelledByHigh) this.maxH.attr('aria-labelledby', this.options.ariaLabelledByHigh) } }, /** * Updates aria attributes according to current values */ updateAriaAttributes: function() { this.minH.attr({ 'aria-valuenow': this.scope.rzSliderModel, 'aria-valuetext': this.customTrFn( this.scope.rzSliderModel, this.options.id, 'model' ), 'aria-valuemin': this.minValue, 'aria-valuemax': this.maxValue, }) if (this.range) { this.maxH.attr({ 'aria-valuenow': this.scope.rzSliderHigh, 'aria-valuetext': this.customTrFn( this.scope.rzSliderHigh, this.options.id, 'high' ), 'aria-valuemin': this.minValue, 'aria-valuemax': this.maxValue, }) } }, /** * Calculate dimensions that are dependent on view port size * * Run once during initialization and every time view port changes size. * * @returns {undefined} */ calcViewDimensions: function() { var handleWidth = this.getDimension(this.minH) this.handleHalfDim = handleWidth / 2 this.barDimension = this.getDimension(this.fullBar) this.maxPos = this.barDimension - handleWidth this.getDimension(this.sliderElem) this.sliderElem.rzsp = this.sliderElem[0].getBoundingClientRect()[ this.positionProperty ] if (this.initHasRun) { this.updateFloorLab() this.updateCeilLab() this.initHandles() var self = this $timeout(function() { self.updateTicksScale() }) } }, /** * Update the ticks position * * @returns {undefined} */ updateTicksScale: function() { if (!this.options.showTicks) return var ticksArray = this.options.ticksArray || this.getTicksArray(), translate = this.options.vertical ? 'translateY' : 'translateX', self = this if (this.options.rightToLeft) ticksArray.reverse() this.scope.ticks = ticksArray.map(function(value) { var legend = null if (angular.isObject(value)) { legend = value.legend value = value.value } var position = self.valueToPosition(value) if (self.options.vertical) position = self.maxPos - position var translation = translate + '(' + Math.round(position) + 'px)' var tick = { legend: legend, selected: self.isTickSelected(value), style: { '-webkit-transform': translation, '-moz-transform': translation, '-o-transform': translation, '-ms-transform': translation, transform: translation, }, } if (tick.selected && self.options.getSelectionBarColor) { tick.style['background-color'] = self.getSelectionBarColor() } if (!tick.selected && self.options.getTickColor) { tick.style['background-color'] = self.getTickColor(value) } if (self.options.ticksTooltip) { tick.tooltip = self.options.ticksTooltip(value) tick.tooltipPlacement = self.options.vertical ? 'right' : 'top' } if ( self.options.showTicksValues === true || value % self.options.showTicksValues === 0 ) { tick.value = self.getDisplayValue(value, 'tick-value') if (self.options.ticksValuesTooltip) { tick.valueTooltip = self.options.ticksValuesTooltip(value) tick.valueTooltipPlacement = self.options.vertical ? 'right' : 'top' } } if (self.getLegend) { legend = self.getLegend(value, self.options.id) if (legend) tick.legend = legend } return tick }) }, getTicksArray: function() { var step = this.step, ticksArray = [] if (this.intermediateTicks) step = this.options.showTicks for ( var value = this.minValue; value <= this.maxValue; value += step ) { ticksArray.push(value) } return ticksArray }, isTickSelected: function(value) { if (!this.range) { if (this.options.showSelectionBarFromValue !== null) { var center = this.options.showSelectionBarFromValue if ( this.lowValue > center && value >= center && value <= this.lowValue ) return true else if ( this.lowValue < center && value <= center && value >= this.lowValue ) return true } else if (this.options.showSelectionBarEnd) { if (value >= this.lowValue) return true } else if (this.options.showSelectionBar && value <= this.lowValue) return true } if (this.range && value >= this.lowValue && value <= this.highValue) return true return false }, /** * Update position of the floor label * * @returns {undefined} */ updateFloorLab: function() { this.translateFn(this.minValue, this.flrLab, 'floor') this.getDimension(this.flrLab) var position = this.options.rightToLeft ? this.barDimension - this.flrLab.rzsd : 0 this.setPosition(this.flrLab, position) }, /** * Update position of the ceiling label * * @returns {undefined} */ updateCeilLab: function() { this.translateFn(this.maxValue, this.ceilLab, 'ceil') this.getDimension(this.ceilLab) var position = this.options.rightToLeft ? 0 : this.barDimension - this.ceilLab.rzsd this.setPosition(this.ceilLab, position) }, /** * Update slider handles and label positions * * @param {string} which * @param {number} newPos */ updateHandles: function(which, newPos) { if (which === 'lowValue') this.updateLowHandle(newPos) else this.updateHighHandle(newPos) this.updateSelectionBar() this.updateTicksScale() if (this.range) this.updateCmbLabel() }, /** * Helper function to work out the position for handle labels depending on RTL or not * * @param {string} labelName maxLab or minLab * @param newPos * * @returns {number} */ getHandleLabelPos: function(labelName, newPos) { var labelRzsd = this[labelName].rzsd, nearHandlePos = newPos - labelRzsd / 2 + this.handleHalfDim, endOfBarPos = this.barDimension - labelRzsd if (!this.options.boundPointerLabels) return nearHandlePos if ( (this.options.rightToLeft && labelName === 'minLab') || (!this.options.rightToLeft && labelName === 'maxLab') ) { return Math.min(nearHandlePos, endOfBarPos) } else { return Math.min(Math.max(nearHandlePos, 0), endOfBarPos) } }, /** * Update low slider handle position and label * * @param {number} newPos * @returns {undefined} */ updateLowHandle: function(newPos) { this.setPosition(this.minH, newPos) this.translateFn(this.lowValue, this.minLab, 'model') this.setPosition( this.minLab, this.getHandleLabelPos('minLab', newPos) ) if (this.options.getPointerColor) { var pointercolor = this.getPointerColor('min') this.scope.minPointerStyle = { backgroundColor: pointercolor, } } if (this.options.autoHideLimitLabels) { this.shFloorCeil() } }, /** * Update high slider handle position and label * * @param {number} newPos * @returns {undefined} */ updateHighHandle: function(newPos) { this.setPosition(this.maxH, newPos) this.translateFn(this.highValue, this.maxLab, 'high') this.setPosition( this.maxLab, this.getHandleLabelPos('maxLab', newPos) ) if (this.options.getPointerColor) { var pointercolor = this.getPointerColor('max') this.scope.maxPointerStyle = { backgroundColor: pointercolor, } } if (this.options.autoHideLimitLabels) { this.shFloorCeil() } }, /** * Show/hide floor/ceiling label * * @returns {undefined} */ shFloorCeil: function() { // Show based only on hideLimitLabels if pointer labels are hidden if (this.options.hidePointerLabels) { return } var flHidden = false, clHidden = false, isMinLabAtFloor = this.isLabelBelowFloorLab(this.minLab), isMinLabAtCeil = this.isLabelAboveCeilLab(this.minLab), isMaxLabAtCeil = this.isLabelAboveCeilLab(this.maxLab), isCmbLabAtFloor = this.isLabelBelowFloorLab(this.cmbLab), isCmbLabAtCeil = this.isLabelAboveCeilLab(this.cmbLab) if (isMinLabAtFloor) { flHidden = true this.hideEl(this.flrLab) } else { flHidden = false this.showEl(this.flrLab) } if (isMinLabAtCeil) { clHidden = true this.hideEl(this.ceilLab) } else { clHidden = false this.showEl(this.ceilLab) } if (this.range) { var hideCeil = this.cmbLabelShown ? isCmbLabAtCeil : isMaxLabAtCeil var hideFloor = this.cmbLabelShown ? isCmbLabAtFloor : isMinLabAtFloor if (hideCeil) { this.hideEl(this.ceilLab) } else if (!clHidden) { this.showEl(this.ceilLab) } // Hide or show floor label if (hideFloor) { this.hideEl(this.flrLab) } else if (!flHidden) { this.showEl(this.flrLab) } } }, isLabelBelowFloorLab: function(label) { var isRTL = this.options.rightToLeft, pos = label.rzsp, dim = label.rzsd, floorPos = this.flrLab.rzsp, floorDim = this.flrLab.rzsd return isRTL ? pos + dim >= floorPos - 2 : pos <= floorPos + floorDim + 2 }, isLabelAboveCeilLab: function(label) { var isRTL = this.options.rightToLeft, pos = label.rzsp, dim = label.rzsd, ceilPos = this.ceilLab.rzsp, ceilDim = this.ceilLab.rzsd return isRTL ? pos <= ceilPos + ceilDim + 2 : pos + dim >= ceilPos - 2 }, /** * Update restricted area bar * * @returns {undefined} */ updateRestrictionBar: function() { var position = 0, dimension = 0 if (this.options.restrictedRange) { var from = this.valueToPosition(this.options.restrictedRange.from), to = this.valueToPosition(this.options.restrictedRange.to) dimension = Math.abs(to - from) position = this.options.rightToLeft ? to + this.handleHalfDim : from + this.handleHalfDim this.setDimension(this.restrictedBar, dimension) this.setPosition(this.restrictedBar, position) } }, /** * Update slider selection bar, combined label and range label * * @returns {undefined} */ updateSelectionBar: function() { var position = 0, dimension = 0, isSelectionBarFromRight = this.options.rightToLeft ? !this.options.showSelectionBarEnd : this.options.showSelectionBarEnd, positionForRange = this.options.rightToLeft ? this.maxH.rzsp + this.handleHalfDim : this.minH.rzsp + this.handleHalfDim if (this.range) { dimension = Math.abs(this.maxH.rzsp - this.minH.rzsp) position = positionForRange } else { if (this.options.showSelectionBarFromValue !== null) { var center = this.options.showSelectionBarFromValue, centerPosition = this.valueToPosition(center), isModelGreaterThanCenter = this.options.rightToLeft ? this.lowValue <= center : this.lowValue > center if (isModelGreaterThanCenter) { dimension = this.minH.rzsp - centerPosition position = centerPosition + this.handleHalfDim } else { dimension = centerPosition - this.minH.rzsp position = this.minH.rzsp + this.handleHalfDim } } else if (isSelectionBarFromRight) { dimension = Math.abs(this.maxPos - this.minH.rzsp) + this.handleHalfDim position = this.minH.rzsp + this.handleHalfDim } else { dimension = this.minH.rzsp + this.handleHalfDim position = 0 } } this.setDimension(this.selBar, dimension) this.setPosition(this.selBar, position) if (this.range && this.options.showOuterSelectionBars) { if (this.options.rightToLeft) { this.setDimension(this.rightOutSelBar, position) this.setPosition(this.rightOutSelBar, 0) this.setDimension( this.leftOutSelBar, this.getDimension(this.fullBar) - (position + dimension) ) this.setPosition(this.leftOutSelBar, position + dimension) } else { this.setDimension(this.leftOutSelBar, position) this.setPosition(this.leftOutSelBar, 0) this.setDimension( this.rightOutSelBar, this.getDimension(this.fullBar) - (position + dimension) ) this.setPosition(this.rightOutSelBar, position + dimension) } } if (this.options.getSelectionBarColor) { var color = this.getSelectionBarColor() this.scope.barStyle = { backgroundColor: color, } } else if (this.options.selectionBarGradient) { var offset = this.options.showSelectionBarFromValue !== null ? this.valueToPosition(this.options.showSelectionBarFromValue) : 0, reversed = (offset - position > 0) ^ isSelectionBarFromRight, direction = this.options.vertical ? reversed ? 'bottom' : 'top' : reversed ? 'left' : 'right' this.scope.barStyle = { backgroundImage: 'linear-gradient(to ' + direction + ', ' + this.options.selectionBarGradient.from + ' 0%,' + this.options.selectionBarGradient.to + ' 100%)', } if (this.options.vertical) { this.scope.barStyle.backgroundPosition = 'center ' + (offset + dimension + position + (reversed ? -this.handleHalfDim : 0)) + 'px' this.scope.barStyle.backgroundSize = '100% ' + (this.barDimension - this.handleHalfDim) + 'px' } else { this.scope.barStyle.backgroundPosition = offset - position + (reversed ? this.handleHalfDim : 0) + 'px center' this.scope.barStyle.backgroundSize = this.barDimension - this.handleHalfDim + 'px 100%' } } }, /** * Wrapper around the getSelectionBarColor of the user to pass to * correct parameters */ getSelectionBarColor: function() { if (this.range) return this.options.getSelectionBarColor( this.scope.rzSliderModel, this.scope.rzSliderHigh ) return this.options.getSelectionBarColor(this.scope.rzSliderModel) }, /** * Wrapper around the getPointerColor of the user to pass to * correct parameters */ getPointerColor: function(pointerType) { if (pointerType === 'max') { return this.options.getPointerColor( this.scope.rzSliderHigh, pointerType ) } return this.options.getPointerColor( this.scope.rzSliderModel, pointerType ) }, /** * Wrapper around the getTickColor of the user to pass to * correct parameters */ getTickColor: function(value) { return this.options.getTickColor(value) }, /** * Update combined label position and value * * @returns {undefined} */ updateCmbLabel: function() { var isLabelOverlap = null if (this.options.rightToLeft) { isLabelOverlap = this.minLab.rzsp - this.minLab.rzsd - 10 <= this.maxLab.rzsp } else { isLabelOverlap = this.minLab.rzsp + this.minLab.rzsd + 10 >= this.maxLab.rzsp } if (isLabelOverlap) { var lowTr = this.getDisplayValue(this.lowValue, 'model'), highTr = this.getDisplayValue(this.highValue, 'high'), labelVal = '' if (this.options.mergeRangeLabelsIfSame && lowTr === highTr) { labelVal = lowTr } else { labelVal = this.options.rightToLeft ? highTr + this.options.labelOverlapSeparator + lowTr : lowTr + this.options.labelOverlapSeparator + highTr } this.translateFn(labelVal, this.cmbLab, 'cmb', false) var pos = this.options.boundPointerLabels ? Math.min( Math.max( this.selBar.rzsp + this.selBar.rzsd / 2 - this.cmbLab.rzsd / 2, 0 ), this.barDimension - this.cmbLab.rzsd ) : this.selBar.rzsp + this.selBar.rzsd / 2 - this.cmbLab.rzsd / 2 this.setPosition(this.cmbLab, pos) th