angularjs-slider
Version:
AngularJS slider directive with no external dependencies. Mobile friendly!.
1,563 lines (1,421 loc) • 92 kB
JavaScript
/*! 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