<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="rzslider.css" />
<link data-require="bootstrap@3.3.7" data-semver="3.3.7" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<script data-require="angular.js@1.6.0" data-semver="1.6.0" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>
<script data-require="ui-bootstrap@*" data-semver="2.2.0" src="https://cdn.rawgit.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-tpls-2.2.0.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="rzslider.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="dateRangeDemo" ng-controller="dateRangeCtrl">
<h2>Slider with Single Date</h2>
<rzslider
rz-slider-model="slider_dates.value"
rz-slider-options="slider_dates.options">
</rzslider>
<h2>Slider with Date Range</h2>
<rzslider
rz-slider-model="dateRangeSlider.minValue"
rz-slider-high="dateRangeSlider.maxValue"
rz-slider-options="dateRangeSlider.options">
</rzslider>
</body>
</html>
// Code goes here
(() => {
angular
.module('dateRangeDemo', ['ui.bootstrap', 'rzModule'])
.controller('dateRangeCtrl', function dateRangeCtrl($scope) {
var vm = this;
// Single Date Slider
var dates = [];
for (var i = 1; i <= 31; i++) {
dates.push(new Date(2016, 7, i));
}
$scope.slider_dates = {
value: new Date(2016, 7, 15),
options: {
stepsArray: dates,
translate: function(date) {
if (date !== null)
return date.toDateString();
return '';
}
}
};
// Date Range Slider
var floorDate = new Date(2015, 0, 1).getTime();
var ceilDate = new Date(2015, 0, 31).getTime();
var minDate = new Date(2015, 0, 11).getTime();
var maxDate = new Date(2015, 0, 20).getTime();
var millisInDay = 24*60*60*1000;
var monthNames =
[
"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
];
var formatDate = function (date_millis)
{
var date = new Date(date_millis);
return date.getDate()+"-"+monthNames[date.getMonth()]+"-"+date.getFullYear();
}
//Range slider config
$scope.dateRangeSlider = {
minValue: minDate,
maxValue: maxDate,
options: {
floor: floorDate,
ceil: ceilDate,
step: millisInDay,
showTicks: false,
draggableRange: true,
translate: function(date_millis) {
if ((date_millis !== null)) {
var dateFromMillis = new Date(date_millis);
// console.log("date_millis="+date_millis);
// return dateFromMillis.toDateString();
return formatDate(dateFromMillis);
}
return '';
}
}
};
});
})();
/* Styles go here */
/*! angularjs-slider - v5.8.7 -
(c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com> -
https://github.com/angular-slider/angularjs-slider -
2016-11-09 */
/*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 = 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('rzModule', [])
.factory('RzSliderOptions', function() {
var defaultOptions = {
floor: 0,
ceil: null, //defaults to rz-slider-model
step: 1,
precision: 0,
minRange: null,
maxRange: 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,
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,
onStart: null,
onChange: null,
onEnd: null,
rightToLeft: false,
boundPointerLabels: true,
mergeRangeLabelsIfSame: false,
customTemplateScope: null,
logScale: false,
customValueToPosition: null,
customPositionToValue: null
};
var globalOptions = {};
var factory = {};
/**
* `options({})` allows global configuration of all sliders in the
* application.
*
* var app = angular.module( 'App', ['rzModule'], 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;
// 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.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;
// 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.$watch('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();
}, true);
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';
}
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.manageElementsStyle();
this.addAccessibility();
this.setMinAndMax();
this.updateCeilLab();
this.updateFloorLab();
this.unbindEvents();
this.manageEventsBindings();
this.setDisabledState();
this.calcViewDimensions();
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.fullBar = jElem;
break;
case 1:
this.selBar = jElem;
break;
case 2:
this.minH = jElem;
break;
case 3:
this.maxH = jElem;
break;
case 4:
this.flrLab = jElem;
break;
case 5:
this.ceilLab = jElem;
break;
case 6:
this.minLab = jElem;
break;
case 7:
this.maxLab = jElem;
break;
case 8:
this.cmbLab = jElem;
break;
case 9:
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);
if (this.options.vertical)
this.sliderElem.addClass('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;
},
/**
* 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');
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');
}
},
/**
* 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 position = self.valueToPosition(value);
if (self.options.vertical)
position = self.maxPos - position;
var tick = {
selected: self.isTickSelected(value),
style: {
transform: translate + '(' + Math.round(position) + 'px)'
}
};
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) {
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) {
var 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 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 = Math.abs(this.maxH.rzsp - this.minH.rzsp) + this.handleHalfDim;
position = 0;
}
}
this.setDimension(this.selBar, dimension);
this.setPosition(this.selBar, position);
if (this.options.getSelectionBarColor) {
var color = this.getSelectionBarColor();
this.scope.barStyle = {
backgroundColor: color
};
}
},
/**
* 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 + ' - ' + lowTr : lowTr + ' - ' + 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);
this.cmbLabelShown = true;
this.hideEl(this.minLab);
this.hideEl(this.maxLab);
this.showEl(this.cmbLab);
} else {
this.cmbLabelShown = false;
this.showEl(this.maxLab);
this.showEl(this.minLab);
this.hideEl(this.cmbLab);
}
if (this.options.autoHideLimitLabels) {
this.shFloorCeil();
}
},
/**
* Return the translated value if a translate function is provided else the original value
* @param value
* @param which if it's min or max handle
* @returns {*}
*/
getDisplayValue: function(value, which) {
if (this.options.stepsArray && !this.options.bindIndexForStepsArray) {
value = this.getStepValue(value);
}
return this.customTrFn(value, this.options.id, which);
},
/**
* Round value to step and precision based on minValue
*
* @param {number} value
* @param {number} customStep a custom step to override the defined step
* @returns {number}
*/
roundStep: function(value, customStep) {
var step = customStep ? customStep : this.step,
steppedDifference = parseFloat((value - this.minValue) / step).toPrecision(12);
steppedDifference = Math.round(+steppedDifference) * step;
var newValue = (this.minValue + steppedDifference).toFixed(this.precision);
return +newValue;
},
/**
* Hide element
*
* @param element
* @returns {jqLite} The jqLite wrapped DOM element
*/
hideEl: function(element) {
return element.css({
visibility: 'hidden'
});
},
/**
* Show element
*
* @param element The jqLite wrapped DOM element
* @returns {jqLite} The jqLite
*/
showEl: function(element) {
if (!!element.rzAlwaysHide) {
return element;
}
return element.css({
visibility: 'visible'
});
},
/**
* Set element left/top position depending on whether slider is horizontal or vertical
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} pos
* @returns {number}
*/
setPosition: function(elem, pos) {
elem.rzsp = pos;
var css = {};
css[this.positionProperty] = Math.round(pos) + 'px';
elem.css(css);
return pos;
},
/**
* Get element width/height depending on whether slider is horizontal or vertical
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @returns {number}
*/
getDimension: function(elem) {
var val = elem[0].getBoundingClientRect();
if (this.options.vertical)
elem.rzsd = (val.bottom - val.top) * this.options.scale;
else
elem.rzsd = (val.right - val.left) * this.options.scale;
return elem.rzsd;
},
/**
* Set element width/height depending on whether slider is horizontal or vertical
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} dim
* @returns {number}
*/
setDimension: function(elem, dim) {
elem.rzsd = dim;
var css = {};
css[this.dimensionProperty] = Math.round(dim) + 'px';
elem.css(css);
return dim;
},
/**
* Returns a value that is within slider range
*
* @param {number} val
* @returns {number}
*/
sanitizeValue: function(val) {
return Math.min(Math.max(val, this.minValue), this.maxValue);
},
/**
* Translate value to pixel position
*
* @param {number} val
* @returns {number}
*/
valueToPosition: function(val) {
var fn = this.linearValueToPosition;
if (this.options.customValueToPosition)
fn = this.options.customValueToPosition;
else if (this.options.logScale)
fn = this.logValueToPosition;
val = this.sanitizeValue(val);
var percent = fn(val, this.minValue, this.maxValue) || 0;
if (this.options.rightToLeft)
percent = 1 - percent;
return percent * this.maxPos;
},
linearValueToPosition: function(val, minVal, maxVal) {
var range = maxVal - minVal;
return (val - minVal) / range;
},
logValueToPosition: function(val, minVal, maxVal) {
val = Math.log(val);
minVal = Math.log(minVal);
maxVal = Math.log(maxVal);
var range = maxVal - minVal;
return (val - minVal) / range;
},
/**
* Translate position to model value
*
* @param {number} position
* @returns {number}
*/
positionToValue: function(position) {
var percent = position / this.maxPos;
if (this.options.rightToLeft)
percent = 1 - percent;
var fn = this.linearPositionToValue;
if (this.options.customPositionToValue)
fn = this.options.customPositionToValue;
else if (this.options.logScale)
fn = this.logPositionToValue;
return fn(percent, this.minValue, this.maxValue) || 0;
},
linearPositionToValue: function(percent, minVal, maxVal) {
return percent * (maxVal - minVal) + minVal;
},
logPositionToValue: function(percent, minVal, maxVal) {
minVal = Math.log(minVal);
maxVal = Math.log(maxVal);
var value = percent * (maxVal - minVal) + minVal;
return Math.exp(value);
},
// Events
/**
* Get the X-coordinate or Y-coordinate of an event
*
* @param {Object} event The event
* @returns {number}
*/
getEventXY: function(event) {
/* http://stackoverflow.com/a/12336075/282882 */
//noinspection JSLint
var clientXY = this.options.vertical ? 'clientY' : 'clientX';
if (event[clientXY] !== undefined) {
return event[clientXY];
}
return event.originalEvent === undefined ?
event.touches[0][clientXY] : event.originalEvent.touches[0][clientXY];
},
/**
* Compute the event position depending on whether the slider is horizontal or vertical
* @param event
* @returns {number}
*/
getEventPosition: function(event) {
var sliderPos = this.sliderElem.rzsp,
eventPos = 0;
if (this.options.vertical)
eventPos = -this.getEventXY(event) + sliderPos;
else
eventPos = this.getEventXY(event) - sliderPos;
return eventPos * this.options.scale - this.handleHalfDim; // #346 handleHalfDim is already scaled
},
/**
* Get event names for move and event end
*
* @param {Event} event The event
*
* @return {{moveEvent: string, endEvent: string}}
*/
getEventNames: function(event) {
var eventNames = {
moveEvent: '',
endEvent: ''
};
if (event.touches || (event.originalEvent !== undefined && event.originalEvent.touches)) {
eventNames.moveEvent = 'touchmove';
eventNames.endEvent = 'touchend';
} else {
eventNames.moveEvent = 'mousemove';
eventNames.endEvent = 'mouseup';
}
return eventNames;
},
/**
* Get the handle closest to an event.
*
* @param event {Event} The event
* @returns {jqLite} The handle closest to the event.
*/
getNearestHandle: function(event) {
if (!this.range) {
return this.minH;
}
var position = this.getEventPosition(event),
distanceMin = Math.abs(position - this.minH.rzsp),
distanceMax = Math.abs(position - this.maxH.rzsp);
if (distanceMin < distanceMax)
return this.minH;
else if (distanceMin > distanceMax)
return this.maxH;
else if (!this.options.rightToLeft)
//if event is at the same distance from min/max then if it's at left of minH, we return minH else maxH
return position < this.minH.rzsp ? this.minH : this.maxH;
else
//reverse in rtl
return position > this.minH.rzsp ? this.minH : this.maxH;
},
/**
* Wrapper function to focus an angular element
*
* @param el {AngularElement} the element to focus
*/
focusElement: function(el) {
var DOM_ELEMENT = 0;
el[DOM_ELEMENT].focus();
},
/**
* Bind mouse and touch events to slider handles
*
* @returns {undefined}
*/
bindEvents: function() {
var barTracking, barStart, barMove;
if (this.options.draggableRange) {
barTracking = 'rzSliderDrag';
barStart = this.onDragStart;
barMove = this.onDragMove;
} else {
barTracking = 'lowValue';
barStart = this.onStart;
barMove = this.onMove;
}
if (!this.options.onlyBindHandles) {
this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking));
this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar));
}
if (this.options.draggableRangeOnly) {
this.minH.on('mousedown', angular.bind(this, barStart, null, barTracking));
this.maxH.on('mousedown', angular.bind(this, barStart, null, barTracking));
} else {
this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'lowValue'));
if (this.range) {
this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'highValue'));
}
if (!this.options.onlyBindHandles) {
this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null));
this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar));
this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null));
this.ticks.on('mousedown', angular.bind(this, this.onTickClick, this.ticks));
}
}
if (!this.options.onlyBindHandles) {
this.selBar.on('touchstart', angular.bind(this, barStart, null, barTracking));
this.selBar.on('touchstart', angular.bind(this, barMove, this.selBar));
}
if (this.options.draggableRangeOnly) {
this.minH.on('touchstart', angular.bind(this, barStart, null, barTracking));
this.maxH.on('touchstart', angular.bind(this, barStart, null, barTracking));
} else {
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'lowValue'));
if (this.range) {
this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'highValue'));
}
if (!this.options.onlyBindHandles) {
this.fullBar.on('touchstart', angular.bind(this, this.onStart, null, null));
this.fullBar.on('touchstart', angular.bind(this, this.onMove, this.fullBar));
this.ticks.on('touchstart', angular.bind(this, this.onStart, null, null));
this.ticks.on('touchstart', angular.bind(this, this.onTickClick, this.ticks));
}
}
if (this.options.keyboardSupport) {
this.minH.on('focus', angular.bind(this, this.onPointerFocus, this.minH, 'lowValue'));
if (this.range) {
this.maxH.on('focus', angular.bind(this, this.onPointerFocus, this.maxH, 'highValue'));
}
}
},
/**
* Unbind mouse and touch events to slider handles
*
* @returns {undefined}
*/
unbindEvents: function() {
this.minH.off();
this.maxH.off();
this.fullBar.off();
this.selBar.off();
this.ticks.off();
},
/**
* onStart event handler
*
* @param {?Object} pointer The jqLite wrapped DOM element; if null, the closest handle is used
* @param {?string} ref The name of the handle being changed; if null, the closest handle's value is modified
* @param {Event} event The event
* @returns {undefined}
*/
onStart: function(pointer, ref, event) {
var ehMove, ehEnd,
eventNames = this.getEventNames(event);
event.stopPropagation();
event.preventDefault();
// We have to do this in case the HTML where the sliders are on
// have been animated into view.
this.calcViewDimensions();
if (pointer) {
this.tracking = ref;
} else {
pointer = this.getNearestHandle(event);
this.tracking = pointer === this.minH ? 'lowValue' : 'highValue';
}
pointer.addClass('rz-active');
if (this.options.keyboardSupport)
this.focusElement(pointer);
ehMove = angular.bind(this, this.dragging.active ? this.onDragMove : this.onMove, pointer);
ehEnd = angular.bind(this, this.onEnd, ehMove);
$document.on(eventNames.moveEvent, ehMove);
$document.one(eventNames.endEvent, ehEnd);
this.callOnStart();
},
/**
* onMove event handler
*
* @param {jqLite} pointer
* @param {Event} event The event
* @param {boolean} fromTick if the event occured on a tick or not
* @returns {undefined}
*/
onMove: function(pointer, event, fromTick) {
var newPos = this.getEventPosition(event),
newValue,
ceilValue = this.options.rightToLeft ? this.minValue : this.maxValue,
flrValue = this.options.rightToLeft ? this.maxValue : this.minValue;
if (newPos <= 0) {
newValue = flrValue;
} else if (newPos >= this.maxPos) {
newValue = ceilValue;
} else {
newValue = this.positionToValue(newPos);
if (fromTick && angular.isNumber(this.options.showTicks))
newValue = this.roundStep(newValue, this.options.showTicks);
else
newValue = this.roundStep(newValue);
}
this.positionTrackingHandle(newValue);
},
/**
* onEnd event handler
*
* @param {Event} event The event
* @param {Function} ehMove The the bound move event handler
* @returns {undefined}
*/
onEnd: function(ehMove, event) {
var moveEventName = this.getEventNames(event).moveEvent;
if (!this.options.keyboardSupport) {
this.minH.removeClass('rz-active');
this.maxH.removeClass('rz-active');
this.tracking = '';
}
this.dragging.active = false;
$document.off(moveEventName, ehMove);
this.callOnEnd();
},
onTickClick: function(pointer, event) {
this.onMove(pointer, event, true);
},
onPointerFocus: function(pointer, ref) {
this.tracking = ref;
pointer.one('blur', angular.bind(this, this.onPointerBlur, pointer));
pointer.on('keydown', angular.bind(this, this.onKeyboardEvent));
pointer.on('keyup', angular.bind(this, this.onKeyUp));
this.firstKeyDown = true;
pointer.addClass('rz-active');
this.currentFocusElement = {
pointer: pointer,
ref: ref
};
},
onKeyUp: function() {
this.firstKeyDown = true;
this.callOnEnd();
},
onPointerBlur: function(pointer) {
pointer.off('keydown');
pointer.off('keyup');
this.tracking = '';
pointer.removeClass('rz-active');
this.currentFocusElement = null
},
/**
* Key actions helper function
*
* @param {number} currentValue value of the slider
*
* @returns {?Object} action value mappings
*/
getKeyActions: function(currentValue) {
var increaseStep = currentValue + this.step,
decreaseStep = currentValue - this.step,
increasePage = currentValue + this.valueRange / 10,
decreasePage = currentValue - this.valueRange / 10;
//Left to right default actions
var actions = {
'UP': increaseStep,
'DOWN': decreaseStep,
'LEFT': decreaseStep,
'RIGHT': increaseStep,
'PAGEUP': increasePage,
'PAGEDOWN': decreasePage,
'HOME': this.minValue,
'END': this.maxValue
};
//right to left means swapping right and left arrows
if (this.options.rightToLeft) {
actions.LEFT = increaseStep;
actions.RIGHT = decreaseStep;
// right to left and vertical means we also swap up and down
if (this.options.vertical) {
actions.UP = decreaseStep;
actions.DOWN = increaseStep;
}
}
return actions;
},
onKeyboardEvent: function(event) {
var currentValue = this[this.tracking],
keyCode = event.keyCode || event.which,
keys = {
38: 'UP',
40: 'DOWN',
37: 'LEFT',
39: 'RIGHT',
33: 'PAGEUP',
34: 'PAGEDOWN',
36: 'HOME',
35: 'END'
},
actions = this.getKeyActions(currentValue),
key = keys[keyCode],
action = actions[key];
if (action == null || this.tracking === '') return;
event.preventDefault();
if (this.firstKeyDown) {
this.firstKeyDown = false;
this.callOnStart();
}
var self = this;
$timeout(function() {
var newValue = self.roundStep(self.sanitizeValue(action));
if (!self.options.draggableRangeOnly) {
self.positionTrackingHandle(newValue);
}
else {
var difference = self.highValue - self.lowValue,
newMinValue, newMaxValue;
if (self.tracking === 'lowValue') {
newMinValue = newValue;
newMaxValue = newValue + difference;
if (newMaxValue > self.maxValue) {
newMaxValue = self.maxValue;
newMinValue = newMaxValue - difference;
}
} else {
newMaxValue = newValue;
newMinValue = newValue - difference;
if (newMinValue < self.minValue) {
newMinValue = self.minValue;
newMaxValue = newMinValue + difference;
}
}
self.positionTrackingBar(newMinValue, newMaxValue);
}
});
},
/**
* onDragStart event handler
*
* Handles dragging of the middle bar.
*
* @param {Object} pointer The jqLite wrapped DOM element
* @param {string} ref One of the refLow, refHigh values
* @param {Event} event The event
* @returns {undefined}
*/
onDragStart: function(pointer, ref, event) {
var position = this.getEventPosition(event);
this.dragging = {
active: true,
value: this.positionToValue(position),
difference: this.highValue - this.lowValue,
lowLimit: this.options.rightToLeft ? this.minH.rzsp - position : position - this.minH.rzsp,
highLimit: this.options.rightToLeft ? position - this.maxH.rzsp : this.maxH.rzsp - position
};
this.onStart(pointer, ref, event);
},
/**
* getValue helper function
*
* gets max or min value depending on whether the newPos is outOfBounds above or below the bar and rightToLeft
*
* @param {string} type 'max' || 'min' The value we are calculating
* @param {number} newPos The new position
* @param {boolean} outOfBounds Is the new position above or below the max/min?
* @param {boolean} isAbove Is the new position above the bar if out of bounds?
*
* @returns {number}
*/
getValue: function(type, newPos, outOfBounds, isAbove) {
var isRTL = this.options.rightToLeft,
value = null;
if (type === 'min') {
if (outOfBounds) {
if (isAbove) {
value = isRTL ? this.minValue : this.maxValue - this.dragging.difference;
} else {
value = isRTL ? this.maxValue - this.dragging.difference : this.minValue;
}
} else {
value = isRTL ? this.positionToValue(newPos + this.dragging.lowLimit) : this.positionToValue(newPos - this.dragging.lowLimit)
}
} else {
if (outOfBounds) {
if (isAbove) {
value = isRTL ? this.minValue + this.dragging.difference : this.maxValue;
} else {
value = isRTL ? this.maxValue : this.minValue + this.dragging.difference;
}
} else {
if (isRTL) {
value = this.positionToValue(newPos + this.dragging.lowLimit) + this.dragging.difference
} else {
value = this.positionToValue(newPos - this.dragging.lowLimit) + this.dragging.difference;
}
}
}
return this.roundStep(value);
},
/**
* onDragMove event handler
*
* Handles dragging of the middle bar.
*
* @param {jqLite} pointer
* @param {Event} event The event
* @returns {undefined}
*/
onDragMove: function(pointer, event) {
var newPos = this.getEventPosition(event),
newMinValue, newMaxValue,
ceilLimit, flrLimit,
isUnderFlrLimit, isOverCeilLimit,
flrH, ceilH;
if (this.options.rightToLeft) {
ceilLimit = this.dragging.lowLimit;
flrLimit = this.dragging.highLimit;
flrH = this.maxH;
ceilH = this.minH;
} else {
ceilLimit = this.dragging.highLimit;
flrLimit = this.dragging.lowLimit;
flrH = this.minH;
ceilH = this.maxH;
}
isUnderFlrLimit = newPos <= flrLimit;
isOverCeilLimit = newPos >= this.maxPos - ceilLimit;
if (isUnderFlrLimit) {
if (flrH.rzsp === 0)
return;
newMinValue = this.getValue('min', newPos, true, false);
newMaxValue = this.getValue('max', newPos, true, false);
} else if (isOverCeilLimit) {
if (ceilH.rzsp === this.maxPos)
return;
newMaxValue = this.getValue('max', newPos, true, true);
newMinValue = this.getValue('min', newPos, true, true);
} else {
newMinValue = this.getValue('min', newPos, false);
newMaxValue = this.getValue('max', newPos, false);
}
this.positionTrackingBar(newMinValue, newMaxValue);
},
/**
* Set the new value and position for the entire bar
*
* @param {number} newMinValue the new minimum value
* @param {number} newMaxValue the new maximum value
*/
positionTrackingBar: function(newMinValue, newMaxValue) {
if (this.options.minLimit != null && newMinValue < this.options.minLimit) {
newMinValue = this.options.minLimit;
newMaxValue = newMinValue + this.dragging.difference;
}
if (this.options.maxLimit != null && newMaxValue > this.options.maxLimit) {
newMaxValue = this.options.maxLimit;
newMinValue = newMaxValue - this.dragging.difference;
}
this.lowValue = newMinValue;
this.highValue = newMaxValue;
this.applyLowValue();
if (this.range)
this.applyHighValue();
this.applyModel();
this.updateHandles('lowValue', this.valueToPosition(newMinValue));
this.updateHandles('highValue', this.valueToPosition(newMaxValue));
},
/**
* Set the new value and position to the current tracking handle
*
* @param {number} newValue new model value
*/
positionTrackingHandle: function(newValue) {
var valueChanged = false;
newValue = this.applyMinMaxLimit(newValue);
if (this.range) {
if (this.options.pushRange) {
newValue = this.applyPushRange(newValue);
valueChanged = true;
}
else {
if (this.options.noSwitching) {
if (this.tracking === 'lowValue' && newValue > this.highValue)
newValue = this.applyMinMaxRange(this.highValue);
else if (this.tracking === 'highValue' && newValue < this.lowValue)
newValue = this.applyMinMaxRange(this.lowValue);
}
newValue = this.applyMinMaxRange(newValue);
/* This is to check if we need to switch the min and max handles */
if (this.tracking === 'lowValue' && newValue > this.highValue) {
this.lowValue = this.highValue;
this.applyLowValue();
this.updateHandles(this.tracking, this.maxH.rzsp);
this.updateAriaAttributes();
this.tracking = 'highValue';
this.minH.removeClass('rz-active');
this.maxH.addClass('rz-active');
if (this.options.keyboardSupport)
this.focusElement(this.maxH);
valueChanged = true;
}
else if (this.tracking === 'highValue' && newValue < this.lowValue) {
this.highValue = this.lowValue;
this.applyHighValue();
this.updateHandles(this.tracking, this.minH.rzsp);
this.updateAriaAttributes();
this.tracking = 'lowValue';
this.maxH.removeClass('rz-active');
this.minH.addClass('rz-active');
if (this.options.keyboardSupport)
this.focusElement(this.minH);
valueChanged = true;
}
}
}
if (this[this.tracking] !== newValue) {
this[this.tracking] = newValue;
if (this.tracking === 'lowValue')
this.applyLowValue();
else
this.applyHighValue();
this.updateHandles(this.tracking, this.valueToPosition(newValue));
this.updateAriaAttributes();
valueChanged = true;
}
if (valueChanged)
this.applyModel();
},
applyMinMaxLimit: function(newValue) {
if (this.options.minLimit != null && newValue < this.options.minLimit)
return this.options.minLimit;
if (this.options.maxLimit != null && newValue > this.options.maxLimit)
return this.options.maxLimit;
return newValue;
},
applyMinMaxRange: function(newValue) {
var oppositeValue = this.tracking === 'lowValue' ? this.highValue : this.lowValue,
difference = Math.abs(newValue - oppositeValue);
if (this.options.minRange != null) {
if (difference < this.options.minRange) {
if (this.tracking === 'lowValue')
return this.highValue - this.options.minRange;
else
return this.lowValue + this.options.minRange;
}
}
if (this.options.maxRange != null) {
if (difference > this.options.maxRange) {
if (this.tracking === 'lowValue')
return this.highValue - this.options.maxRange;
else
return this.lowValue + this.options.maxRange;
}
}
return newValue;
},
applyPushRange: function(newValue) {
var difference = this.tracking === 'lowValue' ? this.highValue - newValue : newValue - this.lowValue,
minRange = this.options.minRange !== null ? this.options.minRange : this.options.step,
maxRange = this.options.maxRange;
// if smaller than minRange
if (difference < minRange) {
if (this.tracking === 'lowValue') {
this.highValue = Math.min(newValue + minRange, this.maxValue);
newValue = this.highValue - minRange;
this.applyHighValue();
this.updateHandles('highValue', this.valueToPosition(this.highValue));
}
else {
this.lowValue = Math.max(newValue - minRange, this.minValue);
newValue = this.lowValue + minRange;
this.applyLowValue();
this.updateHandles('lowValue', this.valueToPosition(this.lowValue));
}
this.updateAriaAttributes();
}
// if greater than maxRange
else if (maxRange !== null && difference > maxRange) {
if (this.tracking === 'lowValue') {
this.highValue = newValue + maxRange;
this.applyHighValue();
this.updateHandles('highValue', this.valueToPosition(this.highValue));
}
else {
this.lowValue = newValue - maxRange;
this.applyLowValue();
this.updateHandles('lowValue', this.valueToPosition(this.lowValue));
}
this.updateAriaAttributes();
}
return newValue;
},
/**
* Apply the model values using scope.$apply.
* We wrap it with the internalChange flag to avoid the watchers to be called
*/
applyModel: function() {
this.internalChange = true;
this.scope.$apply();
this.callOnChange();
this.internalChange = false;
},
/**
* Call the onStart callback if defined
* The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
*
* @returns {undefined}
*/
callOnStart: function() {
if (this.options.onStart) {
var self = this,
pointerType = this.tracking === 'lowValue' ? 'min' : 'max';
this.scope.$evalAsync(function() {
self.options.onStart(self.options.id, self.scope.rzSliderModel, self.scope.rzSliderHigh, pointerType);
});
}
},
/**
* Call the onChange callback if defined
* The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
*
* @returns {undefined}
*/
callOnChange: function() {
if (this.options.onChange) {
var self = this,
pointerType = this.tracking === 'lowValue' ? 'min' : 'max';
this.scope.$evalAsync(function() {
self.options.onChange(self.options.id, self.scope.rzSliderModel, self.scope.rzSliderHigh, pointerType);
});
}
},
/**
* Call the onEnd callback if defined
* The callback call is wrapped in a $evalAsync to ensure that its result will be applied to the scope.
*
* @returns {undefined}
*/
callOnEnd: function() {
if (this.options.onEnd) {
var self = this,
pointerType = this.tracking === 'lowValue' ? 'min' : 'max';
this.scope.$evalAsync(function() {
self.options.onEnd(self.options.id, self.scope.rzSliderModel, self.scope.rzSliderHigh, pointerType);
});
}
this.scope.$emit('slideEnded');
}
};
return Slider;
}])
.directive('rzslider', ['RzSlider', function(RzSlider) {
'use strict';
return {
restrict: 'AE',
replace: true,
scope: {
rzSliderModel: '=?',
rzSliderHigh: '=?',
rzSliderOptions: '&?',
rzSliderTplUrl: '@'
},
/**
* Return template URL
*
* @param {jqLite} elem
* @param {Object} attrs
* @return {string}
*/
templateUrl: function(elem, attrs) {
//noinspection JSUnresolvedVariable
return attrs.rzSliderTplUrl || 'rzSliderTpl.html';
},
link: function(scope, elem) {
scope.slider = new RzSlider(scope, elem); //attach on scope so we can test it
}
};
}]);
// IDE assist
/**
* @name ngScope
*
* @property {number} rzSliderModel
* @property {number} rzSliderHigh
* @property {Object} rzSliderOptions
*/
/**
* @name jqLite
*
* @property {number|undefined} rzsp rzslider label position position
* @property {number|undefined} rzsd rzslider element dimension
* @property {string|undefined} rzsv rzslider label value/text
* @property {Function} css
* @property {Function} text
*/
/**
* @name Event
* @property {Array} touches
* @property {Event} originalEvent
*/
/**
* @name ThrottleOptions
*
* @property {boolean} leading
* @property {boolean} trailing
*/
module.run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('rzSliderTpl.html',
"<div class=rzslider><span class=rz-bar-wrapper><span class=rz-bar></span></span> <span class=rz-bar-wrapper><span class=\"rz-bar rz-selection\" ng-style=barStyle></span></span> <span class=\"rz-pointer rz-pointer-min\" ng-style=minPointerStyle></span> <span class=\"rz-pointer rz-pointer-max\" ng-style=maxPointerStyle></span> <span class=\"rz-bubble rz-limit rz-floor\"></span> <span class=\"rz-bubble rz-limit rz-ceil\"></span> <span class=rz-bubble></span> <span class=rz-bubble></span> <span class=rz-bubble></span><ul ng-show=showTicks class=rz-ticks><li ng-repeat=\"t in ticks track by $index\" class=rz-tick ng-class=\"{'rz-selected': t.selected}\" ng-style=t.style ng-attr-uib-tooltip=\"{{ t.tooltip }}\" ng-attr-tooltip-placement={{t.tooltipPlacement}} ng-attr-tooltip-append-to-body=\"{{ t.tooltip ? true : undefined}}\"><span ng-if=\"t.value != null\" class=rz-tick-value ng-attr-uib-tooltip=\"{{ t.valueTooltip }}\" ng-attr-tooltip-placement={{t.valueTooltipPlacement}}>{{ t.value }}</span> <span ng-if=\"t.legend != null\" class=rz-tick-legend>{{ t.legend }}</span></li></ul></div>"
);
}]);
return module.name
}));
/*! angularjs-slider - v5.8.7 -
(c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com> -
https://github.com/angular-slider/angularjs-slider -
2016-11-09 */
.rzslider {
position: relative;
display: inline-block;
width: 100%;
height: 4px;
margin: 35px 0 15px 0;
vertical-align: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.rzslider.with-legend {
margin-bottom: 40px;
}
.rzslider[disabled] {
cursor: not-allowed;
}
.rzslider[disabled] .rz-pointer {
cursor: not-allowed;
background-color: #d8e0f3;
}
.rzslider[disabled] .rz-bar-wrapper.rz-draggable {
cursor: not-allowed;
}
.rzslider[disabled] .rz-bar.rz-selection {
background: #8b91a2;
}
.rzslider[disabled] .rz-ticks .rz-tick {
cursor: not-allowed;
}
.rzslider[disabled] .rz-ticks .rz-tick.rz-selected {
background: #8b91a2;
}
.rzslider span {
position: absolute;
display: inline-block;
white-space: nowrap;
}
.rzslider .rz-base {
width: 100%;
height: 100%;
padding: 0;
}
.rzslider .rz-bar-wrapper {
left: 0;
z-index: 1;
width: 100%;
height: 32px;
padding-top: 16px;
margin-top: -16px;
box-sizing: border-box;
}
.rzslider .rz-bar-wrapper.rz-draggable {
cursor: move;
}
.rzslider .rz-bar {
left: 0;
z-index: 1;
width: 100%;
height: 4px;
background: #d8e0f3;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
.rzslider .rz-bar.rz-selection {
z-index: 2;
background: #0db9f0;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
.rzslider .rz-pointer {
top: -14px;
z-index: 3;
width: 32px;
height: 32px;
cursor: pointer;
background-color: #0db9f0;
-webkit-border-radius: 16px;
-moz-border-radius: 16px;
border-radius: 16px;
}
.rzslider .rz-pointer:after {
position: absolute;
top: 12px;
left: 12px;
width: 8px;
height: 8px;
background: #ffffff;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
content: '';
}
.rzslider .rz-pointer:hover:after {
background-color: #ffffff;
}
.rzslider .rz-pointer.rz-active {
z-index: 4;
}
.rzslider .rz-pointer.rz-active:after {
background-color: #451aff;
}
.rzslider .rz-bubble {
bottom: 16px;
padding: 1px 3px;
color: #55637d;
cursor: default;
}
.rzslider .rz-bubble.rz-selection {
top: 16px;
}
.rzslider .rz-bubble.rz-limit {
color: #55637d;
}
.rzslider .rz-ticks {
position: absolute;
top: -3px;
left: 0;
z-index: 1;
width: 100%;
height: 0;
margin: 0;
list-style: none;
box-sizing: border-box;
}
.rzslider .rz-ticks .rz-tick {
position: absolute;
top: 0;
left: 0;
width: 10px;
height: 10px;
margin-left: 11px;
text-align: center;
cursor: pointer;
background: #d8e0f3;
border-radius: 50%;
}
.rzslider .rz-ticks .rz-tick.rz-selected {
background: #0db9f0;
}
.rzslider .rz-ticks .rz-tick .rz-tick-value {
position: absolute;
top: -30px;
transform: translate(-50%, 0);
}
.rzslider .rz-ticks .rz-tick .rz-tick-legend {
position: absolute;
top: 24px;
max-width: 50px;
white-space: normal;
transform: translate(-50%, 0);
}
.rzslider .rz-ticks.rz-ticks-values-under .rz-tick-value {
top: initial;
bottom: -32px;
}
.rzslider.rz-vertical {
position: relative;
width: 4px;
height: 100%;
padding: 0;
margin: 0 20px;
vertical-align: baseline;
}
.rzslider.rz-vertical .rz-base {
width: 100%;
height: 100%;
padding: 0;
}
.rzslider.rz-vertical .rz-bar-wrapper {
top: auto;
left: 0;
width: 32px;
height: 100%;
padding: 0 0 0 16px;
margin: 0 0 0 -16px;
}
.rzslider.rz-vertical .rz-bar {
bottom: 0;
left: auto;
width: 4px;
height: 100%;
}
.rzslider.rz-vertical .rz-pointer {
top: auto;
bottom: 0;
left: -14px !important;
}
.rzslider.rz-vertical .rz-bubble {
bottom: 0;
left: 16px !important;
margin-left: 3px;
}
.rzslider.rz-vertical .rz-bubble.rz-selection {
top: auto;
left: 16px !important;
}
.rzslider.rz-vertical .rz-ticks {
top: 0;
left: -3px;
z-index: 1;
width: 0;
height: 100%;
}
.rzslider.rz-vertical .rz-ticks .rz-tick {
margin-top: 11px;
margin-left: auto;
vertical-align: middle;
}
.rzslider.rz-vertical .rz-ticks .rz-tick .rz-tick-value {
top: initial;
left: 24px;
transform: translate(0, -28%);
}
.rzslider.rz-vertical .rz-ticks .rz-tick .rz-tick-legend {
top: initial;
right: 24px;
max-width: none;
white-space: nowrap;
transform: translate(0, -28%);
}
.rzslider.rz-vertical .rz-ticks.rz-ticks-values-under .rz-tick-value {
right: 24px;
bottom: initial;
left: initial;
}