Commit 10998a67 authored by Valentin Hervieu's avatar Valentin Hervieu

Update site and add introduction content

parent ac790b87
* { margin: 0; padding: 0; }
body { font-family: 'Open Sans', sans-serif; color: #1f2636; font-size: 14px; padding-bottom: 40px; }
header { background: #0db9f0; color: #fff; margin: -40px; margin-bottom: 40px; text-align: center; padding: 40px 0; }
h1 { font-weight: 300; }
h2 {margin-bottom:10px;}
.wrapper { background: #fff; padding: 40px; }
article { margin-bottom: 10px; }
.tab-pane{
padding-top: 10px;
* {
margin: 0;
padding: 0;
}
body {
font-family: 'Open Sans', sans-serif;
color: #1f2636;
font-size: 14px;
padding-bottom: 40px;
}
header {
background: #0db9f0;
color: #fff;
margin: -40px;
margin-bottom: 40px;
text-align: center;
padding: 40px 0;
}
h1 {
font-weight: 300;
}
h2 {
margin-bottom: 10px;
}
.wrapper {
background: #fff;
padding: 40px;
}
article {
margin-bottom: 10px;
}
.introduction ul{
margin-left:40px;
}
.tab-pane {
padding-top: 10px;
}
.field-title {
width: 100px;
width: 100px;
}
.vertical-sliders {
margin: 0;
margin: 0;
}
.vertical-sliders > div {
height: 250px;
height: 250px;
}
/*! angularjs-slider - v2.4.1 -
(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-01-15 */
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[disabled] {
cursor: not-allowed;
}
rzslider[disabled] .rz-pointer {
cursor: not-allowed;
background-color: #d8e0f3;
}
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: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;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
width: 100%;
height: 0;
padding: 0 11px;
margin: 0;
list-style: none;
box-sizing: border-box;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
rzslider .rz-ticks .tick {
width: 10px;
height: 10px;
text-align: center;
cursor: pointer;
background: #d8e0f3;
border-radius: 50%;
}
rzslider .rz-ticks .tick.selected {
background: #0db9f0;
}
rzslider .rz-ticks .tick .tick-value {
position: absolute;
top: -30px;
transform: translate(-50%, 0);
}
rzslider.vertical {
position: relative;
width: 4px;
height: 100%;
padding: 0;
margin: 0 20px;
vertical-align: baseline;
}
rzslider.vertical .rz-base {
width: 100%;
height: 100%;
padding: 0;
}
rzslider.vertical .rz-bar-wrapper {
top: auto;
left: 0;
width: 32px;
height: 100%;
padding: 0 0 0 16px;
margin: 0 0 0 -16px;
}
rzslider.vertical .rz-bar {
bottom: 0;
left: auto;
width: 4px;
height: 100%;
}
rzslider.vertical .rz-pointer {
top: auto;
bottom: 0;
left: -14px !important;
}
rzslider.vertical .rz-bubble {
bottom: 0;
left: 16px !important;
margin-left: 3px;
}
rzslider.vertical .rz-bubble.rz-selection {
top: auto;
left: 16px !important;
}
rzslider.vertical .rz-ticks {
top: 0;
left: -3px;
z-index: 1;
width: 0;
height: 100%;
padding: 11px 0;
-webkit-flex-direction: column-reverse;
-ms-flex-direction: column-reverse;
flex-direction: column-reverse;
}
rzslider.vertical .rz-ticks .tick {
vertical-align: middle;
}
rzslider.vertical .rz-ticks .tick .tick-value {
top: auto;
right: -30px;
transform: translate(0, -28%);
}
\ No newline at end of file
/*! angularjs-slider - v2.4.1 -
(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-01-15 */
/*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
module.exports = factory(require('angular'));
} 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,
id: null,
translate: null,
stepsArray: null,
draggableRange: false,
draggableRangeOnly: false,
showSelectionBar: false,
showSelectionBarEnd: false,
hideLimitLabels: false,
readOnly: false,
disabled: false,
interval: 350,
showTicks: false,
showTicksValues: false,
ticksTooltip: null,
ticksValuesTooltip: null,
vertical: false,
selectionBarColor: null,
keyboardSupport: true,
scale: 1,
enforceRange: false,
onlyBindHandles: false,
onStart: null,
onChange: null,
onEnd: 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;
/**
* 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,
offset: 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;
/**
* Set to true if init method already executed
*
* @type {boolean}
*/
this.initHasRun = false;
/**
* Internal flag to prevent watchers to be called when the sliders value are modified internally.
* @type {boolean}
*/
this.internalChange = 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.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();
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);
});
},
/*
* Reflow the slider when the low handle changes (called with throttle)
*/
onLowHandleChange: function() {
this.setMinAndMax();
this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel));
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.setMinAndMax();
this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh));
this.updateSelectionBar();
this.updateTicksScale();
this.updateCmbLabel();
this.updateAriaAttributes();
},
/**
* Read the user options and apply them to the slider model
*/
applyOptions: function() {
this.options = RzSliderOptions.getOptions(this.scope.rzSliderOptions);
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.scope.showTicks = this.options.showTicks; //scope is used in the template
this.options.showSelectionBar = this.options.showSelectionBar || this.options.showSelectionBarEnd;
if (this.options.stepsArray) {
this.options.floor = 0;
this.options.ceil = this.options.stepsArray.length - 1;
this.options.step = 1;
this.customTrFn = function(value) {
return this.options.stepsArray[value];
};
} else if (this.options.translate)
this.customTrFn = this.options.translate;
else
this.customTrFn = function(value) {
return String(value);
};
if (this.options.vertical) {
this.positionProperty = 'bottom';
this.dimensionProperty = 'height';
}
},
/**
* Resets slider
*
* @returns {undefined}
*/
resetSlider: function() {
this.manageElementsStyle();
this.addAccessibility();
this.setMinAndMax();
this.updateCeilLab();
this.updateFloorLab();
this.unbindEvents();
this.manageEventsBindings();
this.setDisabledState();
this.calcViewDimensions();
},
/**
* 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 offset 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);
this.alwaysHide(this.minLab, this.options.showTicksValues);
this.alwaysHide(this.maxLab, this.options.showTicksValues || !this.range);
this.alwaysHide(this.cmbLab, this.options.showTicksValues || !this.range);
this.alwaysHide(this.selBar, !this.range && !this.options.showSelectionBar);
if (this.options.vertical)
this.sliderElem.addClass('vertical');
if (this.options.draggableRange)
this.selBar.addClass('rz-draggable');
else
this.selBar.removeClass('rz-draggable');
},
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.valueToOffset(this.scope.rzSliderModel));
/*
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.valueToOffset(this.scope.rzSliderHigh));
this.updateSelectionBar();
if (this.range)
this.updateCmbLabel();
this.updateTicksScale();
},
/**
* Translate value to human readable format
*
* @param {number|string} value
* @param {jqLite} label
* @param {boolean} [useCustomTr]
* @returns {undefined}
*/
translateFn: function(value, label, useCustomTr) {
useCustomTr = useCustomTr === undefined ? true : useCustomTr;
var valStr = String((useCustomTr ? this.customTrFn(value, this.options.id) : value)),
getDimension = false;
if (label.rzsv === undefined || label.rzsv.length !== valStr.length || (label.rzsv.length > 0 && label.rzsd === 0)) {
getDimension = true;
label.rzsv = valStr;
}
label.text(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.scope.rzSliderModel = this.roundStep(this.scope.rzSliderModel);
if (this.range)
this.scope.rzSliderHigh = this.roundStep(this.scope.rzSliderHigh);
this.minValue = this.roundStep(+this.options.floor);
if (this.options.ceil != null)
this.maxValue = this.roundStep(+this.options.ceil);
else
this.maxValue = this.options.ceil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
if (this.options.enforceRange) {
this.scope.rzSliderModel = this.sanitizeValue(this.scope.rzSliderModel);
if (this.range)
this.scope.rzSliderHigh = this.sanitizeValue(this.scope.rzSliderHigh);
}
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),
'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),
'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();
}
},
/**
* Update the ticks position
*
* @returns {undefined}
*/
updateTicksScale: function() {
if (!this.options.showTicks) return;
var positions = '',
ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1;
this.scope.ticks = [];
for (var i = 0; i < ticksCount; i++) {
var value = this.roundStep(this.minValue + i * this.step);
var tick =   {
selected: this.isTickSelected(value)
};
if (tick.selected && this.options.getSelectionBarColor) {
tick.style = {
'background-color': this.getSelectionBarColor()
};
}
if (this.options.ticksTooltip) {
tick.tooltip = this.options.ticksTooltip(value);
tick.tooltipPlacement = this.options.vertical ? 'right' : 'top';
}
if (this.options.showTicksValues) {
tick.value = this.getDisplayValue(value);
if (this.options.ticksValuesTooltip) {
tick.valueTooltip = this.options.ticksValuesTooltip(value);
tick.valueTooltipPlacement = this.options.vertical ? 'right' : 'top';
}
}
this.scope.ticks.push(tick);
}
},
isTickSelected: function(value) {
if (!this.range && this.options.showSelectionBar && value <= this.scope.rzSliderModel)
return true;
if (this.range && value >= this.scope.rzSliderModel && value <= this.scope.rzSliderHigh)
return true;
return false;
},
/**
* Update position of the ceiling label
*
* @returns {undefined}
*/
updateCeilLab: function() {
this.translateFn(this.maxValue, this.ceilLab);
this.setPosition(this.ceilLab, this.barDimension - this.ceilLab.rzsd);
this.getDimension(this.ceilLab);
},
/**
* Update position of the floor label
*
* @returns {undefined}
*/
updateFloorLab: function() {
this.translateFn(this.minValue, this.flrLab);
this.getDimension(this.flrLab);
},
/**
* Call the onStart callback if defined
*
* @returns {undefined}
*/
callOnStart: function() {
if (this.options.onStart) {
this.options.onStart(this.options.id);
}
},
/**
* Call the onChange callback if defined
*
* @returns {undefined}
*/
callOnChange: function() {
if (this.options.onChange) {
this.options.onChange(this.options.id);
}
},
/**
* Call the onEnd callback if defined
*
* @returns {undefined}
*/
callOnEnd: function() {
if (this.options.onEnd) {
var self = this;
$timeout(function() {
self.options.onEnd(self.options.id);
});
}
},
/**
* Update slider handles and label positions
*
* @param {string} which
* @param {number} newOffset
*/
updateHandles: function(which, newOffset) {
if (which === 'rzSliderModel')
this.updateLowHandle(newOffset);
else if (which === 'rzSliderHigh')
this.updateHighHandle(newOffset);
this.updateSelectionBar();
this.updateTicksScale();
if (this.range)
this.updateCmbLabel();
},
/**
* Update low slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateLowHandle: function(newOffset) {
this.setPosition(this.minH, newOffset);
this.translateFn(this.scope.rzSliderModel, this.minLab);
var pos = Math.min(Math.max(newOffset - this.minLab.rzsd / 2 + this.handleHalfDim, 0), this.barDimension - this.ceilLab.rzsd);
this.setPosition(this.minLab, pos);
this.shFloorCeil();
},
/**
* Update high slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateHighHandle: function(newOffset) {
this.setPosition(this.maxH, newOffset);
this.translateFn(this.scope.rzSliderHigh, this.maxLab);
var pos = Math.min((newOffset - this.maxLab.rzsd / 2 + this.handleHalfDim), (this.barDimension - this.ceilLab.rzsd));
this.setPosition(this.maxLab, pos);
this.shFloorCeil();
},
/**
* Show / hide floor / ceiling label
*
* @returns {undefined}
*/
shFloorCeil: function() {
var flHidden = false,
clHidden = false;
if (this.minLab.rzsp <= this.flrLab.rzsp + this.flrLab.rzsd + 5) {
flHidden = true;
this.hideEl(this.flrLab);
} else {
flHidden = false;
this.showEl(this.flrLab);
}
if (this.minLab.rzsp + this.minLab.rzsd >= this.ceilLab.rzsp - this.handleHalfDim - 10) {
clHidden = true;
this.hideEl(this.ceilLab);
} else {
clHidden = false;
this.showEl(this.ceilLab);
}
if (this.range) {
if (this.maxLab.rzsp + this.maxLab.rzsd >= this.ceilLab.rzsp - 10) {
this.hideEl(this.ceilLab);
} else if (!clHidden) {
this.showEl(this.ceilLab);
}
// Hide or show floor label
if (this.maxLab.rzsp <= this.flrLab.rzsp + this.flrLab.rzsd + this.handleHalfDim) {
this.hideEl(this.flrLab);
} else if (!flHidden) {
this.showEl(this.flrLab);
}
}
},
/**
* Update slider selection bar, combined label and range label
*
* @returns {undefined}
*/
updateSelectionBar: function() {
var position = 0,
dimension = 0;
if (this.range || !this.options.showSelectionBarEnd) {
dimension = Math.abs(this.maxH.rzsp - this.minH.rzsp) + this.handleHalfDim
position = this.range ? this.minH.rzsp + this.handleHalfDim : 0;
} else {
dimension = Math.abs(this.maxPos - this.minH.rzsp) + this.handleHalfDim
position = this.minH.rzsp + this.handleHalfDim;
}
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);
},
/**
* Update combined label position and value
*
* @returns {undefined}
*/
updateCmbLabel: function() {
var lowTr, highTr;
if (this.minLab.rzsp + this.minLab.rzsd + 10 >= this.maxLab.rzsp) {
lowTr = this.getDisplayValue(this.scope.rzSliderModel);
highTr = this.getDisplayValue(this.scope.rzSliderHigh);
this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false);
var pos = Math.min(Math.max((this.selBar.rzsp + this.selBar.rzsd / 2 - this.cmbLab.rzsd / 2), 0), (this.barDimension - this.cmbLab.rzsd));
this.setPosition(this.cmbLab, pos);
this.hideEl(this.minLab);
this.hideEl(this.maxLab);
this.showEl(this.cmbLab);
} else {
this.showEl(this.maxLab);
this.showEl(this.minLab);
this.hideEl(this.cmbLab);
}
},
/**
* Return the translated value if a translate function is provided else the original value
* @param value
* @returns {*}
*/
getDisplayValue: function(value) {
return this.customTrFn(value, this.options.id);
},
/**
* Round value to step and precision
*
* @param {number} value
* @returns {number}
*/
roundStep: function(value) {
var steppedValue = parseFloat(value / this.step).toPrecision(12)
steppedValue = Math.round(steppedValue) * this.step;
steppedValue = steppedValue.toFixed(this.precision);
return +steppedValue;
},
/**
* Hide element
*
* @param element
* @returns {jqLite} The jqLite wrapped DOM element
*/
hideEl: function(element) {
return element.css({
opacity: 0
});
},
/**
* Show element
*
* @param element The jqLite wrapped DOM element
* @returns {jqLite} The jqLite
*/
showEl: function(element) {
if (!!element.rzAlwaysHide) {
return element;
}
return element.css({
opacity: 1
});
},
/**
* Set element left/top offset 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] = 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] = dim + 'px';
elem.css(css);
return dim;
},
/**
* Translate value to pixel offset
*
* @param {number} val
* @returns {number}
*/
valueToOffset: function(val) {
return (this.sanitizeValue(val) - this.minValue) * this.maxPos / this.valueRange || 0;
},
/**
* 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 offset to model value
*
* @param {number} offset
* @returns {number}
*/
offsetToValue: function(offset) {
return (offset / this.maxPos) * this.valueRange + this.minValue;
},
// 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 (clientXY in event) {
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.handleHalfDim) * this.options.scale;
},
/**
* 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 offset = this.getEventPosition(event);
return Math.abs(offset - this.minH.rzsp) < Math.abs(offset - this.maxH.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 = 'rzSliderModel';
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, 'rzSliderModel'));
if (this.range) {
this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
}
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.onMove, 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, 'rzSliderModel'));
if (this.range) {
this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
}
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.onMove, this.ticks));
}
}
if (this.options.keyboardSupport) {
this.minH.on('focus', angular.bind(this, this.onPointerFocus, this.minH, 'rzSliderModel'));
if (this.range) {
this.maxH.on('focus', angular.bind(this, this.onPointerFocus, this.maxH, 'rzSliderHigh'));
}
}
},
/**
* 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 ? 'rzSliderModel' : 'rzSliderHigh';
}
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
* @returns {undefined}
*/
onMove: function(pointer, event) {
var newOffset = this.getEventPosition(event),
newValue;
if (newOffset <= 0) {
if (pointer.rzsp === 0)
return;
newValue = this.minValue;
newOffset = 0;
} else if (newOffset >= this.maxPos) {
if (pointer.rzsp === this.maxPos)
return;
newValue = this.maxValue;
newOffset = this.maxPos;
} else {
newValue = this.offsetToValue(newOffset);
newValue = this.roundStep(newValue);
newOffset = this.valueToOffset(newValue);
}
this.positionTrackingHandle(newValue, newOffset);
},
/**
* 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.scope.$emit('slideEnded');
this.callOnEnd();
},
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.addClass('rz-active');
},
onPointerBlur: function(pointer) {
pointer.off('keydown');
this.tracking = '';
pointer.removeClass('rz-active');
},
onKeyboardEvent: function(event) {
var currentValue = this.scope[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 = {
UP: currentValue + this.step,
DOWN: currentValue - this.step,
LEFT: currentValue - this.step,
RIGHT: currentValue + this.step,
PAGEUP: currentValue + this.valueRange / 10,
PAGEDOWN: currentValue - this.valueRange / 10,
HOME: this.minValue,
END: this.maxValue
},
key = keys[keyCode],
action = actions[key];
if (action == null || this.tracking === '') return;
event.preventDefault();
var newValue = this.roundStep(this.sanitizeValue(action)),
newOffset = this.valueToOffset(newValue);
if (!this.options.draggableRangeOnly) {
this.positionTrackingHandle(newValue, newOffset);
} else {
var difference = this.scope.rzSliderHigh - this.scope.rzSliderModel,
newMinOffset, newMaxOffset,
newMinValue, newMaxValue;
if (this.tracking === 'rzSliderModel') {
newMinValue = newValue;
newMinOffset = newOffset;
newMaxValue = newValue + difference;
if (newMaxValue > this.maxValue) {
newMaxValue = this.maxValue;
newMinValue = newMaxValue - difference;
newMinOffset = this.valueToOffset(newMinValue);
}
newMaxOffset = this.valueToOffset(newMaxValue);
} else {
newMaxValue = newValue;
newMaxOffset = newOffset;
newMinValue = newValue - difference;
if (newMinValue < this.minValue) {
newMinValue = this.minValue;
newMaxValue = newMinValue + difference;
newMaxOffset = this.valueToOffset(newMaxValue);
}
newMinOffset = this.valueToOffset(newMinValue);
}
this.positionTrackingBar(newMinValue, newMaxValue, newMinOffset, newMaxOffset);
}
},
/**
* 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 offset = this.getEventPosition(event);
this.dragging = {
active: true,
value: this.offsetToValue(offset),
difference: this.scope.rzSliderHigh - this.scope.rzSliderModel,
lowLimit: offset - this.minH.rzsp,
highLimit: this.maxH.rzsp - offset
};
this.onStart(pointer, ref, event);
},
/**
* onDragMove event handler
*
* Handles dragging of the middle bar.
*
* @param {jqLite} pointer
* @param {Event} event The event
* @returns {undefined}
*/
onDragMove: function(pointer, event) {
var newOffset = this.getEventPosition(event),
newMinOffset, newMaxOffset,
newMinValue, newMaxValue;
if (newOffset <= this.dragging.lowLimit) {
if (this.minH.rzsp === 0)
return;
newMinValue = this.minValue;
newMinOffset = 0;
newMaxValue = this.minValue + this.dragging.difference;
newMaxOffset = this.valueToOffset(newMaxValue);
} else if (newOffset >= this.maxPos - this.dragging.highLimit) {
if (this.maxH.rzsp === this.maxPos)
return;
newMaxValue = this.maxValue;
newMaxOffset = this.maxPos;
newMinValue = this.maxValue - this.dragging.difference;
newMinOffset = this.valueToOffset(newMinValue);
} else {
newMinValue = this.offsetToValue(newOffset - this.dragging.lowLimit);
newMinValue = this.roundStep(newMinValue);
newMinOffset = this.valueToOffset(newMinValue);
newMaxValue = newMinValue + this.dragging.difference;
newMaxOffset = this.valueToOffset(newMaxValue);
}
this.positionTrackingBar(newMinValue, newMaxValue, newMinOffset, newMaxOffset);
},
/**
* Set the new value and offset for the entire bar
*
* @param {number} newMinValue the new minimum value
* @param {number} newMaxValue the new maximum value
* @param {number} newMinOffset the new minimum offset
* @param {number} newMaxOffset the new maximum offset
*/
positionTrackingBar: function(newMinValue, newMaxValue, newMinOffset, newMaxOffset) {
this.scope.rzSliderModel = newMinValue;
this.scope.rzSliderHigh = newMaxValue;
this.updateHandles('rzSliderModel', newMinOffset);
this.updateHandles('rzSliderHigh', newMaxOffset);
this.applyModel();
},
/**
* Set the new value and offset to the current tracking handle
*
* @param {number} newValue new model value
* @param {number} newOffset new offset value
*/
positionTrackingHandle: function(newValue, newOffset) {
var valueChanged = false;
var switched = false;
if (this.range) {
/* This is to check if we need to switch the min and max handles*/
if (this.tracking === 'rzSliderModel' && newValue >= this.scope.rzSliderHigh) {
switched = true;
this.scope[this.tracking] = this.scope.rzSliderHigh;
this.updateHandles(this.tracking, this.maxH.rzsp);
this.updateAriaAttributes();
this.tracking = 'rzSliderHigh';
this.minH.removeClass('rz-active');
this.maxH.addClass('rz-active');
if (this.options.keyboardSupport)
this.focusElement(this.maxH);
valueChanged = true;
} else if (this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel) {
switched = true;
this.scope[this.tracking] = this.scope.rzSliderModel;
this.updateHandles(this.tracking, this.minH.rzsp);
this.updateAriaAttributes();
this.tracking = 'rzSliderModel';
this.maxH.removeClass('rz-active');
this.minH.addClass('rz-active');
if (this.options.keyboardSupport)
this.focusElement(this.minH);
valueChanged = true;
}
}
if (this.scope[this.tracking] !== newValue) {
this.scope[this.tracking] = newValue;
this.updateHandles(this.tracking, newOffset);
this.updateAriaAttributes();
valueChanged = true;
}
if (valueChanged) {
this.applyModel();
}
return switched;
},
/**
* 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;
}
};
return Slider;
}])
.directive('rzslider', ['RzSlider', function(RzSlider) {
'use strict';
return {
restrict: 'E',
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 offset
* @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',
"<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></span> <span class=rz-pointer></span> <span class=\"rz-bubble rz-limit\"></span> <span class=\"rz-bubble rz-limit\"></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=tick ng-class=\"{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=tick-value ng-attr-uib-tooltip=\"{{ t.valueTooltip }}\" ng-attr-tooltip-placement={{t.valueTooltipPlacement}}>{{ t.value }}</span></li></ul>"
);
}]);
return module
}));
......@@ -2,251 +2,284 @@
<html ng-app="rzSliderDemo">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AngularJS Touch Slider</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" href="https://rawgit.com/rzajac/angularjs-slider/master/dist/rzslider.css"/>
<link rel="stylesheet" href="demo.css"/>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400,700' rel='stylesheet' type='text/css'>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AngularJS Slider</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" href="https://rawgit.com/rzajac/angularjs-slider/master/dist/rzslider.css"/>
<link rel="stylesheet" href="demo.css"/>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400,700' rel='stylesheet' type='text/css'>
</head>
<body ng-controller="MainCtrl">
<div class="wrapper">
<header>
<h1>AngularJS Touch Slider</h1>
</header>
<header>
<h1>AngularJS Slider</h1>
<a href="https://github.com/angular-slider/angularjs-slider"><img
style="position: absolute; top: 0; right: 0; border: 0;"
src="https://camo.githubusercontent.com/38ef81f8aca64bb9a64448d0d70f1308ef5341ab/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67"
alt="Fork me on GitHub"
data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png"></a>
</header>
<article>
<h2>Simple slider</h2>
Model: <input type="number" ng-model="minSlider.value"/><br/>
<rzslider rz-slider-model="minSlider.value"></rzslider>
</article>
<article class="introduction">
<h2>AngularJS slider directive with no external dependencies</h2>
<p>Slider directive implementation for AngularJS, without any dependencies</p>
<p>
<ul>
<li>Mobile friendly</li>
<li>Fast</li>
<li>Well documented</li>
<li>Customizable</li>
<li>Simple to use</li>
<li>Compatibility with jQuery Lite, ie. without full jQuery</li>
</ul>
</p>
<hr>
</article>
<article>
<h2>Range slider</h2>
Min Value: <input type="number" ng-model="minRangeSlider.minValue"/><br/>
Max Value: <input type="number" ng-model="minRangeSlider.maxValue"/><br/>
<rzslider
rz-slider-model="minRangeSlider.minValue"
rz-slider-high="minRangeSlider.maxValue"
rz-slider-options="minRangeSlider.options"
></rzslider>
</article>
<article>
<h2>Simple slider</h2>
Model: <input type="number" ng-model="minSlider.value"/><br/>
<rzslider rz-slider-model="minSlider.value"></rzslider>
</article>
<article>
<h2>Slider with visible selection bar</h2>
<rzslider
rz-slider-model="slider_visible_bar.value"
rz-slider-options="slider_visible_bar.options"
></rzslider>
</article>
<article>
<h2>Range slider</h2>
Min Value: <input type="number" ng-model="minRangeSlider.minValue"/><br/>
Max Value: <input type="number" ng-model="minRangeSlider.maxValue"/><br/>
<rzslider
rz-slider-model="minRangeSlider.minValue"
rz-slider-high="minRangeSlider.maxValue"
rz-slider-options="minRangeSlider.options"
></rzslider>
</article>
<article>
<h2>Slider with dynamic selection bar colors</h2>
<rzslider
rz-slider-model="color_slider_bar.value"
rz-slider-options="color_slider_bar.options"
></rzslider>
</article>
<article>
<h2>Slider with visible selection bar</h2>
<rzslider
rz-slider-model="slider_visible_bar.value"
rz-slider-options="slider_visible_bar.options"
></rzslider>
</article>
<article>
<h2>Slider with custom floor/ceil/step</h2>
<rzslider
rz-slider-model="slider_floor_ceil.value"
rz-slider-options="slider_floor_ceil.options"
></rzslider>
</article>
<article>
<h2>Slider with dynamic selection bar colors</h2>
<rzslider
rz-slider-model="color_slider_bar.value"
rz-slider-options="color_slider_bar.options"
></rzslider>
</article>
<article>
<h2>Slider with callbacks on start, change and end</h2>
<p>Value linked on start: {{ otherData.start }}</p>
<p>Value linked on change: {{ otherData.change }}</p>
<p>Value linked on end: {{ otherData.end }}</p>
<article>
<h2>Slider with custom floor/ceil/step</h2>
<rzslider
rz-slider-model="slider_floor_ceil.value"
rz-slider-options="slider_floor_ceil.options"
></rzslider>
</article>
<rzslider
rz-slider-model="slider_callbacks.value"
rz-slider-options="slider_callbacks.options"
></rzslider>
</article>
<article>
<h2>Slider with callbacks on start, change and end</h2>
<p>Value linked on start: {{ otherData.start }}</p>
<p>Value linked on change: {{ otherData.change }}</p>
<p>Value linked on end: {{ otherData.end }}</p>
<article>
<h2>Slider with custom display function</h2>
<rzslider
rz-slider-model="slider_translate.minValue"
rz-slider-high="slider_translate.maxValue"
rz-slider-options="slider_translate.options"
></rzslider>
</article>
<rzslider
rz-slider-model="slider_callbacks.value"
rz-slider-options="slider_callbacks.options"
></rzslider>
</article>
<article>
<h2>Slider with Alphabet</h2>
<rzslider
rz-slider-model="slider_alphabet.value"
rz-slider-options="slider_alphabet.options"
></rzslider>
</article>
<article>
<h2>Slider with custom display function</h2>
<rzslider
rz-slider-model="slider_translate.minValue"
rz-slider-high="slider_translate.maxValue"
rz-slider-options="slider_translate.options"
></rzslider>
</article>
<article>
<h2>Slider with ticks</h2>
<rzslider
rz-slider-model="slider_ticks.value"
rz-slider-options="slider_ticks.options"
></rzslider>
</article>
<article>
<h2>Slider with Alphabet</h2>
<rzslider
rz-slider-model="slider_alphabet.value"
rz-slider-options="slider_alphabet.options"
></rzslider>
</article>
<article>
<h2>Slider with ticks and tooltips</h2>
<rzslider
rz-slider-model="slider_ticks_tooltip.value"
rz-slider-options="slider_ticks_tooltip.options"
></rzslider>
</article>
<article>
<h2>Slider with ticks</h2>
<rzslider
rz-slider-model="slider_ticks.value"
rz-slider-options="slider_ticks.options"
></rzslider>
</article>
<article>
<h2>Slider with ticks and values (and tooltips)</h2>
<rzslider
rz-slider-model="slider_ticks_values.value"
rz-slider-options="slider_ticks_values.options"
></rzslider>
</article>
<article>
<h2>Slider with ticks and tooltips</h2>
<rzslider
rz-slider-model="slider_ticks_tooltip.value"
rz-slider-options="slider_ticks_tooltip.options"
></rzslider>
</article>
<article>
<h2>Range slider with ticks and values</h2>
<rzslider
rz-slider-model="range_slider_ticks_values.minValue"
rz-slider-high="range_slider_ticks_values.maxValue"
rz-slider-options="range_slider_ticks_values.options"
></rzslider>
</article>
<article>
<h2>Slider with ticks and values (and tooltips)</h2>
<rzslider
rz-slider-model="slider_ticks_values.value"
rz-slider-options="slider_ticks_values.options"
></rzslider>
</article>
<article>
<h2>Slider with draggable range</h2>
<rzslider
rz-slider-model="slider_draggable_range.minValue"
rz-slider-high="slider_draggable_range.maxValue"
rz-slider-options="slider_draggable_range.options"
></rzslider>
</article>
<article>
<h2>Range slider with ticks and values</h2>
<rzslider
rz-slider-model="range_slider_ticks_values.minValue"
rz-slider-high="range_slider_ticks_values.maxValue"
rz-slider-options="range_slider_ticks_values.options"
></rzslider>
</article>
<article>
<h2>Slider with draggable range only</h2>
<rzslider
rz-slider-model="slider_draggable_range_only.minValue"
rz-slider-high="slider_draggable_range_only.maxValue"
rz-slider-options="slider_draggable_range_only.options"
></rzslider>
</article>
<article>
<h2>Slider with draggable range</h2>
<rzslider
rz-slider-model="slider_draggable_range.minValue"
rz-slider-high="slider_draggable_range.maxValue"
rz-slider-options="slider_draggable_range.options"
></rzslider>
</article>
<article>
<h2>Vertical sliders</h2>
<div class="row vertical-sliders" style="margin: 20px;">
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider1.value" rz-slider-options="verticalSlider1.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider2.minValue" rz-slider-high="verticalSlider2.maxValue"
rz-slider-options="verticalSlider2.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider3.value" rz-slider-options="verticalSlider3.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider4.minValue" rz-slider-high="verticalSlider4.maxValue"
rz-slider-options="verticalSlider4.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider5.value" rz-slider-options="verticalSlider5.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider6.value" rz-slider-options="verticalSlider6.options"></rzslider>
</div>
</div>
</article>
<article>
<h2>Slider with draggable range only</h2>
<rzslider
rz-slider-model="slider_draggable_range_only.minValue"
rz-slider-high="slider_draggable_range_only.maxValue"
rz-slider-options="slider_draggable_range_only.options"
></rzslider>
</article>
<article>
<h2>Disabled slider</h2>
<label>Disabled <input type="checkbox" ng-model="disabled_slider.options.disabled"></label>
<rzslider
rz-slider-model="disabled_slider.value"
rz-slider-options="disabled_slider.options"
></rzslider>
</article>
<article>
<h2>Vertical sliders</h2>
<div class="row vertical-sliders" style="margin: 20px;">
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider1.value"
rz-slider-options="verticalSlider1.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider2.minValue" rz-slider-high="verticalSlider2.maxValue"
rz-slider-options="verticalSlider2.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider3.value"
rz-slider-options="verticalSlider3.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider4.minValue" rz-slider-high="verticalSlider4.maxValue"
rz-slider-options="verticalSlider4.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider5.value"
rz-slider-options="verticalSlider5.options"></rzslider>
</div>
<div class="col-md-2">
<rzslider rz-slider-model="verticalSlider6.value"
rz-slider-options="verticalSlider6.options"></rzslider>
</div>
</div>
</article>
<article>
<h2>Read-only slider</h2>
<label>Read-only <input type="checkbox" ng-model="read_only_slider.options.readOnly"></label>
<rzslider
rz-slider-model="read_only_slider.value"
rz-slider-options="read_only_slider.options"
></rzslider>
</article>
<article>
<h2>Disabled slider</h2>
<label>Disabled <input type="checkbox" ng-model="disabled_slider.options.disabled"></label>
<rzslider
rz-slider-model="disabled_slider.value"
rz-slider-options="disabled_slider.options"
></rzslider>
</article>
<article>
<h2>Toggle slider example</h2>
<button ng-click="toggle()">{{ visible ? 'Hide' : 'Show' }}</button>
<br/>
<div ng-show="visible">
<rzslider
rz-slider-model="slider_toggle.value"
rz-slider-options="slider_toggle.options"></rzslider>
</div>
</article>
<article>
<h2>Read-only slider</h2>
<label>Read-only <input type="checkbox" ng-model="read_only_slider.options.readOnly"></label>
<rzslider
rz-slider-model="read_only_slider.value"
rz-slider-options="read_only_slider.options"
></rzslider>
</article>
<article>
<h2>Sliders inside a modal</h2>
Normal slider value: {{percentages.normal.low}}%
</br>
Range slider values: {{percentages.range.low}}% and {{percentages.range.high}}%
</br>
<button type="button" ng-click="openModal()" class="btn btn-default btn-lg">Open Modal!</button>
</article>
<article>
<h2>Toggle slider example</h2>
<button ng-click="toggle()">{{ visible ? 'Hide' : 'Show' }}</button>
<br/>
<div ng-show="visible">
<rzslider
rz-slider-model="slider_toggle.value"
rz-slider-options="slider_toggle.options"></rzslider>
</div>
</article>
<article>
<h2>Sliders inside tabs</h2>
<p>Price 1: {{tabSliders.slider1.value}}</p>
<p>Price 2: {{tabSliders.slider2.value}}</p>
<tabset>
<tab heading="Slider 1" select="refreshSlider()">
<rzslider rz-slider-model="tabSliders.slider1.value"></rzslider>
</tab>
<tab heading="Slider 2" select="refreshSlider()">
<rzslider rz-slider-model="tabSliders.slider2.value"></rzslider>
</tab>
</tabset>
</article>
<article>
<h2>Sliders inside a modal</h2>
Normal slider value: {{percentages.normal.low}}%
</br>
Range slider values: {{percentages.range.low}}% and {{percentages.range.high}}%
</br>
<button type="button" ng-click="openModal()" class="btn btn-default btn-lg">Open Modal!</button>
</article>
<article>
<h2>Slider with all options demo</h2>
<div class="row all-options">
<div class="col-md-4">
<label class="field-title">Min Value: </label><input type="number" ng-model="slider_all_options.minValue"/><br/>
<label class="field-title"><input type="checkbox" ng-click="toggleHighValue()"> Max Value: </label>
<input type="number" ng-model="slider_all_options.maxValue" ng-disabled="slider_all_options.maxValue == null"/><br/>
<label class="field-title">Floor: </label><input type="number" ng-model="slider_all_options.options.floor"/><br/>
<label class="field-title">Ceil: </label><input type="number" ng-model="slider_all_options.options.ceil"/><br/>
</div>
<div class="col-md-4">
<label class="field-title">Step: </label><input type="number" ng-model="slider_all_options.options.step"/><br/>
<label class="field-title">Precision: </label><input type="number" ng-model="slider_all_options.options.precision"/><br/>
<label>Hide limits <input type="checkbox" ng-model="slider_all_options.options.hideLimitLabels"></label><br/>
<label>Draggable range <input type="checkbox" ng-model="slider_all_options.options.draggableRange"></label>
</div>
<div class="col-md-4">
<label>Show ticks <input type="checkbox" ng-model="slider_all_options.options.showTicks"></label><br/>
<label>Show ticks values <input type="checkbox" ng-model="slider_all_options.options.showTicksValues"></label><br/>
<label>Disabled <input type="checkbox" ng-model="slider_all_options.options.disabled"></label><br/>
<label>Read-Only <input type="checkbox" ng-model="slider_all_options.options.readOnly"></label>
</div>
</div>
<rzslider
rz-slider-model="slider_all_options.minValue"
rz-slider-high="slider_all_options.maxValue"
rz-slider-options="slider_all_options.options"
></rzslider>
</article>
<article>
<h2>Sliders inside tabs</h2>
<p>Price 1: {{tabSliders.slider1.value}}</p>
<p>Price 2: {{tabSliders.slider2.value}}</p>
<tabset>
<tab heading="Slider 1" select="refreshSlider()">
<rzslider rz-slider-model="tabSliders.slider1.value"></rzslider>
</tab>
<tab heading="Slider 2" select="refreshSlider()">
<rzslider rz-slider-model="tabSliders.slider2.value"></rzslider>
</tab>
</tabset>
</article>
<article>
<h2>Slider with all options demo</h2>
<div class="row all-options">
<div class="col-md-4">
<label class="field-title">Min Value: </label><input type="number"
ng-model="slider_all_options.minValue"/><br/>
<label class="field-title"><input type="checkbox" ng-click="toggleHighValue()"> Max Value: </label>
<input type="number" ng-model="slider_all_options.maxValue"
ng-disabled="slider_all_options.maxValue == null"/><br/>
<label class="field-title">Floor: </label><input type="number"
ng-model="slider_all_options.options.floor"/><br/>
<label class="field-title">Ceil: </label><input type="number"
ng-model="slider_all_options.options.ceil"/><br/>
</div>
<div class="col-md-4">
<label class="field-title">Step: </label><input type="number"
ng-model="slider_all_options.options.step"/><br/>
<label class="field-title">Precision: </label><input type="number"
ng-model="slider_all_options.options.precision"/><br/>
<label>Hide limits <input type="checkbox" ng-model="slider_all_options.options.hideLimitLabels"></label><br/>
<label>Draggable range <input type="checkbox"
ng-model="slider_all_options.options.draggableRange"></label>
</div>
<div class="col-md-4">
<label>Show ticks <input type="checkbox" ng-model="slider_all_options.options.showTicks"></label><br/>
<label>Show ticks values <input type="checkbox"
ng-model="slider_all_options.options.showTicksValues"></label><br/>
<label>Disabled <input type="checkbox" ng-model="slider_all_options.options.disabled"></label><br/>
<label>Read-Only <input type="checkbox" ng-model="slider_all_options.options.readOnly"></label>
</div>
</div>
<rzslider
rz-slider-model="slider_all_options.minValue"
rz-slider-high="slider_all_options.maxValue"
rz-slider-options="slider_all_options.options"
></rzslider>
</article>
</div>
</body>
......
/**
* Angular JS slider directive
*
* (c) Rafal Zajac <rzajac@gmail.com>
* http://github.com/rzajac/angularjs-slider
*
* Licensed under the MIT license
*/
/* Slider colors */
/* Slider size parameters */
rzslider {
position: relative;
display: inline-block;
width: 100%;
height: 4px;
margin: 30px 0 15px 0;
vertical-align: middle;
}
rzslider span {
position: absolute;
display: inline-block;
white-space: nowrap;
}
rzslider span.rz-base {
width: 100%;
height: 100%;
padding: 0;
}
rzslider span.rz-bar {
z-index: 0;
width: 100%;
height: 100%;
background: #d8e0f3;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
rzslider span.rz-bar.rz-selection {
z-index: 1;
width: 0;
background: #0db9f0;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
rzslider span.rz-pointer {
top: -14px;
z-index: 2;
width: 32px;
height: 32px;
cursor: pointer;
background-color: #0db9f0;
-webkit-border-radius: 16px;
-moz-border-radius: 16px;
border-radius: 16px;
}
rzslider span.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 span.rz-pointer:hover:after {
background-color: #ffffff;
}
rzslider span.rz-pointer.rz-active:after {
background-color: #451aff;
}
rzslider span.rz-bubble {
top: -32px;
padding: 1px 3px;
color: #55637d;
cursor: default;
}
rzslider span.rz-bubble.rz-selection {
top: 16px;
}
rzslider span.rz-bubble.rz-limit {
color: #55637d;
}
\ No newline at end of file
/**
* Angular JS slider directive
*
* (c) Rafal Zajac <rzajac@gmail.com>
* http://github.com/rzajac/angularjs-slider
*
* Version: v0.1.21
*
* Licensed under the MIT license
*/
/*jslint unparam: true */
/*global angular: false, console: false */
angular.module('rzModule', [])
.run(['$templateCache', function($templateCache) {
'use strict';
var template = '<span class="rz-bar"></span>' + // 0 The slider bar
'<span class="rz-bar rz-selection"></span>' + // 1 Highlight between two handles
'<span class="rz-pointer"></span>' + // 2 Left slider handle
'<span class="rz-pointer"></span>' + // 3 Right slider handle
'<span class="rz-bubble rz-limit"></span>' + // 4 Floor label
'<span class="rz-bubble rz-limit"></span>' + // 5 Ceiling label
'<span class="rz-bubble"></span>' + // 6 Label above left slider handle
'<span class="rz-bubble"></span>' + // 7 Label above right slider handle
'<span class="rz-bubble"></span>'; // 8 Range label when the slider handles are close ex. 15 - 17
$templateCache.put('rzSliderTpl.html', template);
}])
.value('throttle',
/**
* throttle
*
* Taken from underscore project
*
* @param {Function} func
* @param {number} wait
* @param {ThrottleOptions} options
* @returns {Function}
*/
function throttle(func, wait, options) {
'use strict';
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 = options.leading === false ? 0 : getTime();
timeout = null;
result = func.apply(context, args);
context = args = null;
};
return function() {
var now = getTime();
if (!previous && options.leading === false) { previous = now; }
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
})
.factory('RzSlider', ['$timeout', '$document', '$window', 'throttle', function($timeout, $document, $window, throttle)
{
'use strict';
/**
* Slider
*
* @param {ngScope} scope The AngularJS scope
* @param {Element} sliderElem The slider directive element wrapped in jqLite
* @param {*} attributes The slider directive attributes
* @constructor
*/
var Slider = function(scope, sliderElem, attributes)
{
/**
* The slider's scope
*
* @type {ngScope}
*/
this.scope = scope;
/**
* The slider attributes
*
* @type {*}
*/
this.attributes = attributes;
/**
* Slider element wrapped in jqLite
*
* @type {jqLite}
*/
this.sliderElem = sliderElem;
/**
* Slider type
*
* @type {boolean} Set to true for range slider
*/
this.range = attributes.rzSliderHigh !== undefined && attributes.rzSliderModel !== undefined;
/**
* Half of the width of the slider handles
*
* @type {number}
*/
this.handleHalfWidth = 0;
/**
* Always show selection bar
*
* @type {boolean}
*/
this.alwaysShowBar = !!attributes.rzSliderAlwaysShowBar;
/**
* Maximum left the slider handle can have
*
* @type {number}
*/
this.maxLeft = 0;
/**
* Precision
*
* @type {number}
*/
this.precision = 0;
/**
* Step
*
* @type {number}
*/
this.step = 0;
/**
* 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;
/**
* Hide limit labels
*
* @type {boolean}
*/
this.hideLimitLabels = !!attributes.rzSliderHideLimitLabels;
/**
* Only present model values
*
* Do not allow to change values
*
* @type {boolean}
*/
this.presentOnly = attributes.rzSliderPresentOnly === 'true';
/**
* The delta between min and max value
*
* @type {number}
*/
this.valueRange = 0;
/**
* Set to true if init method already executed
*
* @type {boolean}
*/
this.initHasRun = false;
/**
* Custom translate function
*
* @type {function}
*/
this.customTrFn = this.scope.rzSliderTranslate() || function(value) { return String(value); };
/**
* Array of de-registration functions to call on $destroy
*
* @type {Array.<Function>}
*/
this.deRegFuncs = [];
// 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
// Initialize slider
this.init();
};
// Add instance methods
Slider.prototype = {
/**
* Initialize slider
*
* @returns {undefined}
*/
init: function()
{
var thrLow, thrHigh, unRegFn,
calcDimFn = angular.bind(this, this.calcViewDimensions),
self = this;
this.initElemHandles();
this.calcViewDimensions();
this.setMinAndMax();
this.precision = this.scope.rzSliderPrecision === undefined ? 0 : +this.scope.rzSliderPrecision;
this.step = this.scope.rzSliderStep === undefined ? 1 : +this.scope.rzSliderStep;
$timeout(function()
{
self.updateCeilLab();
self.updateFloorLab();
self.initHandles();
if (!self.presentOnly) { self.bindEvents(); }
});
// Recalculate slider view dimensions
unRegFn = this.scope.$on('reCalcViewDimensions', calcDimFn);
this.deRegFuncs.push(unRegFn);
// Recalculate stuff if view port dimensions have changed
angular.element($window).on('resize', calcDimFn);
this.initHasRun = true;
// Watch for changes to the model
thrLow = throttle(function()
{
self.setMinAndMax();
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));
self.updateSelectionBar();
if(self.range)
{
self.updateCmbLabel();
}
}, 350, { leading: false });
thrHigh = throttle(function()
{
self.setMinAndMax();
self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
self.updateSelectionBar();
self.updateCmbLabel();
}, 350, { leading: false });
this.scope.$on('rzSliderForceRender', function()
{
self.resetLabelsValue();
thrLow();
if(self.range) { thrHigh(); }
self.resetSlider();
});
// Watchers
unRegFn = this.scope.$watch('rzSliderModel', function(newValue, oldValue)
{
if(newValue === oldValue) { return; }
thrLow();
});
this.deRegFuncs.push(unRegFn);
unRegFn = this.scope.$watch('rzSliderHigh', function(newValue, oldValue)
{
if(newValue === oldValue) { return; }
thrHigh();
});
this.deRegFuncs.push(unRegFn);
this.scope.$watch('rzSliderFloor', function(newValue, oldValue)
{
if(newValue === oldValue) { return; }
self.resetSlider();
});
this.deRegFuncs.push(unRegFn);
unRegFn = this.scope.$watch('rzSliderCeil', function(newValue, oldValue)
{
if(newValue === oldValue) { return; }
self.resetSlider();
});
this.deRegFuncs.push(unRegFn);
this.scope.$on('$destroy', function()
{
self.minH.off();
self.maxH.off();
angular.element($window).off('resize', calcDimFn);
self.deRegFuncs.map(function(unbind) { unbind(); });
});
},
/**
* Resets slider
*
* @returns {undefined}
*/
resetSlider: function()
{
this.setMinAndMax();
this.calcViewDimensions();
this.updateCeilLab();
this.updateFloorLab();
},
/**
* 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.valueToOffset(this.scope.rzSliderModel));
if(this.range)
{
this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh));
this.updateCmbLabel();
}
this.updateSelectionBar();
},
/**
* Translate value to human readable format
*
* @param {number|string} value
* @param {jqLite} label
* @param {bool?} useCustomTr
* @returns {undefined}
*/
translateFn: function(value, label, useCustomTr)
{
useCustomTr = useCustomTr === undefined ? true : useCustomTr;
var valStr = String(useCustomTr ? this.customTrFn(value) : value),
getWidth = false;
if(label.rzsv === undefined || label.rzsv.length !== valStr.length || (label.rzsv.length > 0 && label.rzsw === 0))
{
getWidth = true;
label.rzsv = valStr;
}
label.text(valStr);
// Update width only when length of the label have changed
if(getWidth) { this.getWidth(label); }
},
/**
* Set maximum and minimum values for the slider
*
* @returns {undefined}
*/
setMinAndMax: function()
{
if(this.scope.rzSliderFloor)
{
this.minValue = +this.scope.rzSliderFloor;
}
else
{
this.minValue = this.scope.rzSliderFloor = 0;
}
if(this.scope.rzSliderCeil)
{
this.maxValue = +this.scope.rzSliderCeil;
}
else
{
this.scope.rzSliderCeil = this.maxValue = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
}
if(this.scope.rzSliderStep)
{
this.step = +this.scope.rzSliderStep;
}
this.valueRange = this.maxValue - this.minValue;
},
/**
* 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;
}
}, this);
// Initialize offset cache properties
this.fullBar.rzsl = 0;
this.selBar.rzsl = 0;
this.minH.rzsl = 0;
this.maxH.rzsl = 0;
this.flrLab.rzsl = 0;
this.ceilLab.rzsl = 0;
this.minLab.rzsl = 0;
this.maxLab.rzsl = 0;
this.cmbLab.rzsl = 0;
// Hide limit labels
if(this.hideLimitLabels)
{
this.flrLab.rzAlwaysHide = true;
this.ceilLab.rzAlwaysHide = true;
this.hideEl(this.flrLab);
this.hideEl(this.ceilLab);
}
// Remove stuff not needed in single slider
if(this.range === false)
{
this.cmbLab.remove();
this.maxLab.remove();
// Hide max handle
this.maxH.rzAlwaysHide = true;
this.maxH[0].style.zIndex = '-1000';
this.hideEl(this.maxH);
}
// Show selection bar for single slider or not
if(this.range === false && this.alwaysShowBar === false)
{
this.maxH.remove();
this.selBar.remove();
}
},
/**
* 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.getWidth(this.minH);
this.handleHalfWidth = handleWidth / 2;
this.barWidth = this.getWidth(this.fullBar);
this.maxLeft = this.barWidth - handleWidth;
this.getWidth(this.sliderElem);
this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;
if(this.initHasRun)
{
this.updateCeilLab();
this.initHandles();
}
},
/**
* Update position of the ceiling label
*
* @returns {undefined}
*/
updateCeilLab: function()
{
this.translateFn(this.scope.rzSliderCeil, this.ceilLab);
this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw);
this.getWidth(this.ceilLab);
},
/**
* Update position of the floor label
*
* @returns {undefined}
*/
updateFloorLab: function()
{
this.translateFn(this.scope.rzSliderFloor, this.flrLab);
this.getWidth(this.flrLab);
},
/**
* Update slider handles and label positions
*
* @param {string} which
* @param {number} newOffset
*/
updateHandles: function(which, newOffset)
{
if(which === 'rzSliderModel')
{
this.updateLowHandle(newOffset);
this.updateSelectionBar();
if(this.range)
{
this.updateCmbLabel();
}
return;
}
if(which === 'rzSliderHigh')
{
this.updateHighHandle(newOffset);
this.updateSelectionBar();
if(this.range)
{
this.updateCmbLabel();
}
return;
}
// Update both
this.updateLowHandle(newOffset);
this.updateHighHandle(newOffset);
this.updateSelectionBar();
this.updateCmbLabel();
},
/**
* Update low slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateLowHandle: function(newOffset)
{
var delta = Math.abs(this.minH.rzsl - newOffset);
if(delta <= 0 && delta < 1) { return; }
this.setLeft(this.minH, newOffset);
this.translateFn(this.scope.rzSliderModel, this.minLab);
this.setLeft(this.minLab, newOffset - this.minLab.rzsw / 2 + this.handleHalfWidth);
this.shFloorCeil();
},
/**
* Update high slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateHighHandle: function(newOffset)
{
this.setLeft(this.maxH, newOffset);
this.translateFn(this.scope.rzSliderHigh, this.maxLab);
this.setLeft(this.maxLab, newOffset - this.maxLab.rzsw / 2 + this.handleHalfWidth);
this.shFloorCeil();
},
/**
* Show / hide floor / ceiling label
*
* @returns {undefined}
*/
shFloorCeil: function()
{
var flHidden = false, clHidden = false;
if(this.minLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + 5)
{
flHidden = true;
this.hideEl(this.flrLab);
}
else
{
flHidden = false;
this.showEl(this.flrLab);
}
if(this.minLab.rzsl + this.minLab.rzsw >= this.ceilLab.rzsl - this.handleHalfWidth - 10)
{
clHidden = true;
this.hideEl(this.ceilLab);
}
else
{
clHidden = false;
this.showEl(this.ceilLab);
}
if(this.range)
{
if(this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10)
{
this.hideEl(this.ceilLab);
}
else if( ! clHidden)
{
this.showEl(this.ceilLab);
}
// Hide or show floor label
if(this.maxLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + this.handleHalfWidth)
{
this.hideEl(this.flrLab);
}
else if( ! flHidden)
{
this.showEl(this.flrLab);
}
}
},
/**
* Update slider selection bar, combined label and range label
*
* @returns {undefined}
*/
updateSelectionBar: function()
{
this.setWidth(this.selBar, Math.abs(this.maxH.rzsl - this.minH.rzsl));
this.setLeft(this.selBar, this.range ? this.minH.rzsl + this.handleHalfWidth : 0);
},
/**
* Update combined label position and value
*
* @returns {undefined}
*/
updateCmbLabel: function()
{
var lowTr, highTr;
if(this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl)
{
if(this.customTrFn)
{
lowTr = this.customTrFn(this.scope.rzSliderModel);
highTr = this.customTrFn(this.scope.rzSliderHigh);
}
else
{
lowTr = this.scope.rzSliderModel;
highTr = this.scope.rzSliderHigh;
}
this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false);
this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2);
this.hideEl(this.minLab);
this.hideEl(this.maxLab);
this.showEl(this.cmbLab);
}
else
{
this.showEl(this.maxLab);
this.showEl(this.minLab);
this.hideEl(this.cmbLab);
}
},
/**
* Round value to step and precision
*
* @param {number} value
* @returns {number}
*/
roundStep: function(value)
{
var step = this.step,
remainder = +((value - this.minValue) % step).toFixed(3),
steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;
steppedValue = steppedValue.toFixed(this.precision);
return +steppedValue;
},
/**
* Hide element
*
* @param element
* @returns {jqLite} The jqLite wrapped DOM element
*/
hideEl: function (element)
{
return element.css({opacity: 0});
},
/**
* Show element
*
* @param element The jqLite wrapped DOM element
* @returns {jqLite} The jqLite
*/
showEl: function (element)
{
if(!!element.rzAlwaysHide) { return element; }
return element.css({opacity: 1});
},
/**
* Set element left offset
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} left
* @returns {number}
*/
setLeft: function (elem, left)
{
elem.rzsl = left;
elem.css({left: left + 'px'});
return left;
},
/**
* Get element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @returns {number}
*/
getWidth: function(elem)
{
var val = elem[0].getBoundingClientRect();
elem.rzsw = val.right - val.left;
return elem.rzsw;
},
/**
* Set element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} width
* @returns {*}
*/
setWidth: function(elem, width)
{
elem.rzsw = width;
elem.css({width: width + 'px'});
return width;
},
/**
* Translate value to pixel offset
*
* @param {number} val
* @returns {number}
*/
valueToOffset: function(val)
{
return (val - this.minValue) * this.maxLeft / this.valueRange;
},
/**
* Translate offset to model value
*
* @param {number} offset
* @returns {number}
*/
offsetToValue: function(offset)
{
return (offset / this.maxLeft) * this.valueRange + this.minValue;
},
// Events
/**
* Bind mouse and touch events to slider handles
*
* @returns {undefined}
*/
bindEvents: function()
{
this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
if(this.range) { this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); }
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
if(this.range) { this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); }
},
/**
* onStart event handler
*
* @param {Object} pointer The jqLite wrapped DOM element
* @param {string} ref One of the refLow, refHigh values
* @param {Event} event The event
* @returns {undefined}
*/
onStart: function (pointer, ref, event)
{
var ehMove, ehEnd,
eventNames = this.getEventNames(event);
event.stopPropagation();
event.preventDefault();
if(this.tracking !== '') { return; }
// We have to do this in case the HTML where the sliders are on
// have been animated into view.
this.calcViewDimensions();
this.tracking = ref;
pointer.addClass('rz-active');
ehMove = angular.bind(this, this.onMove, pointer);
ehEnd = angular.bind(this, this.onEnd, ehMove);
$document.on(eventNames.moveEvent, ehMove);
$document.one(eventNames.endEvent, ehEnd);
},
/**
* onMove event handler
*
* @param {jqLite} pointer
* @param {Event} event The event
* @returns {undefined}
*/
onMove: function (pointer, event)
{
var eventX, sliderLO, newOffset, newValue;
/* http://stackoverflow.com/a/12336075/282882 */
//noinspection JSLint
if('clientX' in event)
{
eventX = event.clientX;
}
else
{
eventX = event.originalEvent === undefined ?
event.touches[0].clientX
: event.originalEvent.touches[0].clientX;
}
sliderLO = this.sliderElem.rzsl;
newOffset = eventX - sliderLO - this.handleHalfWidth;
if(newOffset <= 0)
{
if(pointer.rzsl !== 0)
{
this.scope[this.tracking] = this.minValue;
this.updateHandles(this.tracking, 0);
this.scope.$apply();
}
return;
}
if(newOffset >= this.maxLeft)
{
if(pointer.rzsl !== this.maxLeft)
{
this.scope[this.tracking] = this.maxValue;
this.updateHandles(this.tracking, this.maxLeft);
this.scope.$apply();
}
return;
}
newValue = this.offsetToValue(newOffset);
newValue = this.roundStep(newValue);
newOffset = this.valueToOffset(newValue);
if (this.range)
{
if (this.tracking === 'rzSliderModel' && newValue >= this.scope.rzSliderHigh)
{
this.scope[this.tracking] = this.scope.rzSliderHigh;
this.updateHandles(this.tracking, this.maxH.rzsl);
this.tracking = 'rzSliderHigh';
this.minH.removeClass('rz-active');
this.maxH.addClass('rz-active');
}
else if(this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel)
{
this.scope[this.tracking] = this.scope.rzSliderModel;
this.updateHandles(this.tracking, this.minH.rzsl);
this.tracking = 'rzSliderModel';
this.maxH.removeClass('rz-active');
this.minH.addClass('rz-active');
}
}
if(this.scope[this.tracking] !== newValue)
{
this.scope[this.tracking] = newValue;
this.updateHandles(this.tracking, newOffset);
this.scope.$apply();
}
},
/**
* 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;
this.minH.removeClass('rz-active');
this.maxH.removeClass('rz-active');
$document.off(moveEventName, ehMove);
this.scope.$emit('slideEnded');
this.tracking = '';
},
/**
* 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;
}
};
return Slider;
}])
.directive('rzslider', ['RzSlider', function(Slider)
{
'use strict';
return {
restrict: 'E',
scope: {
rzSliderFloor: '=?',
rzSliderCeil: '=?',
rzSliderStep: '@',
rzSliderPrecision: '@',
rzSliderModel: '=?',
rzSliderHigh: '=?',
rzSliderTranslate: '&',
rzSliderHideLimitLabels: '=?',
rzSliderAlwaysShowBar: '=?',
rzSliderPresentOnly: '@'
},
/**
* Return template URL
*
* @param {*} elem
* @param {*} attrs
* @return {string}
*/
templateUrl: function(elem, attrs) {
//noinspection JSUnresolvedVariable
return attrs.rzSliderTplUrl || 'rzSliderTpl.html';
},
link: function(scope, elem, attr)
{
return new Slider(scope, elem, attr);
}
};
}]);
// IDE assist
/**
* @name ngScope
*
* @property {number} rzSliderModel
* @property {number} rzSliderHigh
* @property {number} rzSliderCeil
*/
/**
* @name jqLite
*
* @property {number|undefined} rzsl
* @property {number|undefined} rzsw
* @property {string|undefined} rzsv
* @property {Function} css
* @property {Function} text
*/
/**
* @name Event
* @property {Array} touches
* @property {Event} originalEvent
*/
/**
* @name ThrottleOptions
*
* @property {bool} leading
* @property {bool} trailing
*/
......@@ -6,7 +6,7 @@ git checkout master -- .gitignore
git checkout master -- dist/rzslider.css
git checkout master -- dist/rzslider.js
#mv demo/* ./
mv dist/* ./
# cp bower_components/angular/angular.min.js angular.min.js
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment