Commit d03ac54c authored by Valentin Hervieu's avatar Valentin Hervieu

Apply editorconfig formatting

parent a7c7f0e3
module.exports = function(grunt) { module.exports = function(grunt) {
// Project configuration. // Project configuration.
grunt.initConfig({ grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
minBanner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + minBanner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
'(c) <%= pkg.author %>, <%= pkg.repository.url %> - ' + '(c) <%= pkg.author %>, <%= pkg.repository.url %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %> */\n', '<%= grunt.template.today("yyyy-mm-dd") %> */\n',
recess: { recess: {
options: { options: {
compile: true compile: true
}, },
slider: { slider: {
src: ['src/rzslider.less'], src: ['src/rzslider.less'],
dest: 'dist/rzslider.css' dest: 'dist/rzslider.css'
}, },
min: { min: {
options: { options: {
compress: true, compress: true,
banner: '<%= minBanner %>' banner: '<%= minBanner %>'
},
src: ['dist/rzslider.css'],
dest: 'dist/rzslider.min.css'
}
}, },
src: ['dist/rzslider.css'],
dest: 'dist/rzslider.min.css'
}
},
uglify: { uglify: {
options: { options: {
report: 'min', report: 'min',
banner: '<%= minBanner %>' banner: '<%= minBanner %>'
}, },
rzslider: { rzslider: {
files: { files: {
'dist/rzslider.min.js': [ 'dist/rzslider.min.js': [
'dist/rzslider.js' 'dist/rzslider.js'
] ]
} }
} }
}, },
ngtemplates: { ngtemplates: {
app: { app: {
src: 'src/**.html', src: 'src/**.html',
dest: 'temp/templates.js', dest: 'temp/templates.js',
options: { options: {
htmlmin: { htmlmin: {
collapseBooleanAttributes: true, collapseBooleanAttributes: true,
collapseWhitespace: true, collapseWhitespace: true,
removeAttributeQuotes: true, removeAttributeQuotes: true,
removeComments: true, // Only if you don't use comment directives! removeComments: true, // Only if you don't use comment directives!
removeEmptyAttributes: true, removeEmptyAttributes: true,
removeRedundantAttributes: true, removeRedundantAttributes: true,
removeScriptTypeAttributes: true, removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true removeStyleLinkTypeAttributes: true
}, },
module: 'rzModule', module: 'rzModule',
url: function(url) { url: function(url) {
return url.replace('src/', ''); return url.replace('src/', '');
}, },
bootstrap: function(module, script) { bootstrap: function(module, script) {
return 'module.run(function($templateCache) {\n' + script + '\n});'; return 'module.run(function($templateCache) {\n' + script + '\n});';
} }
} }
} }
}, },
replace: { replace: {
dist: { dist: {
options: { options: {
patterns: [ patterns: [{
{ match: /\/\*templateReplacement\*\//,
match: /\/\*templateReplacement\*\//, replacement: '<%= grunt.file.read("temp/templates.js") %>'
replacement: '<%= grunt.file.read("temp/templates.js") %>' }]
}
]
},
files: [
{expand: true, flatten: true, src: ['src/rzslider.js'], dest: 'dist/'}
]
}
}, },
files: [{
expand: true,
flatten: true,
src: ['src/rzslider.js'],
dest: 'dist/'
}]
}
},
ngAnnotate: { ngAnnotate: {
options: { options: {
singleQuotes: true, singleQuotes: true,
}, },
rzslider: { rzslider: {
files: [{ files: [{
'dist/rzslider.js': 'dist/rzslider.js' 'dist/rzslider.js': 'dist/rzslider.js'
}, { }, {
expand: true, expand: true,
src: ['dist/rzslider.js'] src: ['dist/rzslider.js']
} }]
] }
} },
}, watch: {
watch: { all: {
all: { files: ['dist/*', 'demo/*'],
files: ['dist/*', 'demo/*'], options: {
options: { livereload: true
livereload: true
}
},
js: {
files: ['src/*js', 'src/*.html'],
tasks: ['js']
},
less: {
files: ['src/*.less'],
tasks: ['css']
}
},
serve: {
options: {
port: 9000
}
} }
},
js: {
files: ['src/*js', 'src/*.html'],
tasks: ['js']
},
less: {
files: ['src/*.less'],
tasks: ['css']
}
},
serve: {
options: {
port: 9000
}
}
}); });
grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-recess'); grunt.loadNpmTasks('grunt-recess');
grunt.loadNpmTasks('grunt-angular-templates'); grunt.loadNpmTasks('grunt-angular-templates');
grunt.loadNpmTasks('grunt-replace'); grunt.loadNpmTasks('grunt-replace');
grunt.loadNpmTasks('grunt-ng-annotate'); grunt.loadNpmTasks('grunt-ng-annotate');
grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-serve'); grunt.loadNpmTasks('grunt-serve');
grunt.registerTask('default', ['css', 'js']); grunt.registerTask('default', ['css', 'js']);
grunt.registerTask('css', ['recess']); grunt.registerTask('css', ['recess']);
grunt.registerTask('js', ['ngtemplates', 'replace', 'ngAnnotate', 'uglify']); grunt.registerTask('js', ['ngtemplates', 'replace', 'ngAnnotate', 'uglify']);
}; };
...@@ -31,53 +31,53 @@ ...@@ -31,53 +31,53 @@
'use strict'; 'use strict';
var module = angular.module('rzModule', []) var module = angular.module('rzModule', [])
.factory('RzSliderOptions', function() { .factory('RzSliderOptions', function() {
var defaultOptions = { var defaultOptions = {
floor: 0, floor: 0,
ceil: null, //defaults to rz-slider-model ceil: null, //defaults to rz-slider-model
step: 1, step: 1,
precision: 0, precision: 0,
id: null, id: null,
translate: null, translate: null,
stepsArray: null, stepsArray: null,
draggableRange: false, draggableRange: false,
showSelectionBar: false, showSelectionBar: false,
hideLimitLabels: false, hideLimitLabels: false,
readOnly: false, readOnly: false,
disabled: false, disabled: false,
interval: 350, interval: 350,
showTicks: false, showTicks: false,
showTicksValues: false, showTicksValues: false,
ticksValuesTooltip: null, ticksValuesTooltip: null,
scale: 1, scale: 1,
onStart: null, onStart: null,
onChange: null, onChange: null,
onEnd: null onEnd: null
}; };
var globalOptions = {}; var globalOptions = {};
var factory = {}; var factory = {};
/** /**
* `options({})` allows global configuration of all sliders in the * `options({})` allows global configuration of all sliders in the
* application. * application.
* *
* var app = angular.module( 'App', ['rzModule'], function( RzSliderOptions ) { * var app = angular.module( 'App', ['rzModule'], function( RzSliderOptions ) {
* // show ticks for all sliders * // show ticks for all sliders
* RzSliderOptions.options( { showTicks: true } ); * RzSliderOptions.options( { showTicks: true } );
* }); * });
*/ */
factory.options = function(value) { factory.options = function(value) {
angular.extend(globalOptions, value); angular.extend(globalOptions, value);
}; };
factory.getOptions = function(options) { factory.getOptions = function(options) {
return angular.extend({}, defaultOptions, globalOptions, options); return angular.extend({}, defaultOptions, globalOptions, options);
}; };
return factory; return factory;
}) })
.value('rzThrottle', .value('rzThrottle',
/** /**
* rzThrottle * rzThrottle
* *
...@@ -124,1242 +124,1238 @@ ...@@ -124,1242 +124,1238 @@
}; };
}) })
.factory('RzSlider', ['$timeout', '$document', '$window', '$compile', 'RzSliderOptions', 'rzThrottle', function($timeout, $document, $window, $compile, RzSliderOptions, rzThrottle) { .factory('RzSlider', ['$timeout', '$document', '$window', '$compile', 'RzSliderOptions', 'rzThrottle', function($timeout, $document, $window, $compile, RzSliderOptions, rzThrottle) {
'use strict'; '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) {
/** /**
* Slider * The slider's scope
* *
* @param {ngScope} scope The AngularJS scope * @type {ngScope}
* @param {Element} sliderElem The slider directive element wrapped in jqLite
* @constructor
*/ */
var Slider = function(scope, sliderElem) { this.scope = scope;
/**
* The slider's scope
*
* @type {ngScope}
*/
this.scope = scope;
/** /**
* Slider element wrapped in jqLite * Slider element wrapped in jqLite
* *
* @type {jqLite} * @type {jqLite}
*/ */
this.sliderElem = sliderElem; this.sliderElem = sliderElem;
/** /**
* Slider type * Slider type
* *
* @type {boolean} Set to true for range slider * @type {boolean} Set to true for range slider
*/ */
this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined; this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
/** /**
* Values recorded when first dragging the bar * Values recorded when first dragging the bar
* *
* @type {Object} * @type {Object}
*/ */
this.dragging = { this.dragging = {
active: false, active: false,
value: 0, value: 0,
difference: 0, difference: 0,
offset: 0, offset: 0,
lowDist: 0, lowDist: 0,
highDist: 0 highDist: 0
}; };
/** /**
* Half of the width of the slider handles * Half of the width of the slider handles
* *
* @type {number} * @type {number}
*/ */
this.handleHalfWidth = 0; this.handleHalfWidth = 0;
/** /**
* Maximum left the slider handle can have * Maximum left the slider handle can have
* *
* @type {number} * @type {number}
*/ */
this.maxLeft = 0; this.maxLeft = 0;
/** /**
* Precision * Precision
* *
* @type {number} * @type {number}
*/ */
this.precision = 0; this.precision = 0;
/** /**
* Step * Step
* *
* @type {number} * @type {number}
*/ */
this.step = 0; this.step = 0;
/** /**
* The name of the handle we are currently tracking * The name of the handle we are currently tracking
* *
* @type {string} * @type {string}
*/ */
this.tracking = ''; this.tracking = '';
/** /**
* Minimum value (floor) of the model * Minimum value (floor) of the model
* *
* @type {number} * @type {number}
*/ */
this.minValue = 0; this.minValue = 0;
/** /**
* Maximum value (ceiling) of the model * Maximum value (ceiling) of the model
* *
* @type {number} * @type {number}
*/ */
this.maxValue = 0; this.maxValue = 0;
/** /**
* The delta between min and max value * The delta between min and max value
* *
* @type {number} * @type {number}
*/ */
this.valueRange = 0; this.valueRange = 0;
/** /**
* Set to true if init method already executed * Set to true if init method already executed
* *
* @type {boolean} * @type {boolean}
*/ */
this.initHasRun = false; this.initHasRun = false;
// Slider DOM elements wrapped in jqLite // Slider DOM elements wrapped in jqLite
this.fullBar = null; // The whole slider bar this.fullBar = null; // The whole slider bar
this.selBar = null; // Highlight between two handles this.selBar = null; // Highlight between two handles
this.minH = null; // Left slider handle this.minH = null; // Left slider handle
this.maxH = null; // Right slider handle this.maxH = null; // Right slider handle
this.flrLab = null; // Floor label this.flrLab = null; // Floor label
this.ceilLab = null; // Ceiling label this.ceilLab = null; // Ceiling label
this.minLab = null; // Label above the low value this.minLab = null; // Label above the low value
this.maxLab = null; // Label above the high value this.maxLab = null; // Label above the high value
this.cmbLab = null; // Combined label this.cmbLab = null; // Combined label
this.ticks = null; // The ticks this.ticks = null; // The ticks
// Initialize slider // Initialize slider
this.init(); this.init();
}; };
// Add instance methods
Slider.prototype = {
// Add instance methods /**
Slider.prototype = { * Initialize slider
*
* @returns {undefined}
*/
init: function() {
var thrLow, thrHigh,
calcDimFn = angular.bind(this, this.calcViewDimensions),
self = this;
this.applyOptions();
this.initElemHandles();
this.manageElementsStyle();
this.addAccessibility();
this.manageEventsBindings();
this.setDisabledState();
this.calcViewDimensions();
this.setMinAndMax();
$timeout(function() {
self.updateCeilLab();
self.updateFloorLab();
self.initHandles();
self.bindEvents();
});
// 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.setMinAndMax();
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));
self.updateSelectionBar();
self.updateTicksScale();
if (self.range) {
self.updateCmbLabel();
}
/** }, self.options.interval);
* Initialize slider
* thrHigh = rzThrottle(function() {
* @returns {undefined} self.setMinAndMax();
*/ self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
init: function() { self.updateSelectionBar();
var thrLow, thrHigh, self.updateTicksScale();
calcDimFn = angular.bind(this, this.calcViewDimensions), self.updateCmbLabel();
self = this; }, self.options.interval);
this.applyOptions(); this.scope.$on('rzSliderForceRender', function() {
this.initElemHandles(); self.resetLabelsValue();
this.manageElementsStyle(); thrLow();
this.addAccessibility(); if (self.range) {
this.manageEventsBindings(); thrHigh();
this.setDisabledState(); }
this.calcViewDimensions(); self.resetSlider();
this.setMinAndMax(); });
$timeout(function() { // Watchers
self.updateCeilLab(); this.scope.$watch('rzSliderModel', function(newValue, oldValue) {
self.updateFloorLab(); if (newValue === oldValue)
self.initHandles(); return;
self.bindEvents(); thrLow();
}); });
// Recalculate slider view dimensions this.scope.$watch('rzSliderHigh', function(newValue, oldValue) {
this.scope.$on('reCalcViewDimensions', calcDimFn); if (newValue === oldValue)
return;
if (newValue != null)
thrHigh();
if (self.range && newValue == null || !self.range && newValue != null) {
self.applyOptions();
self.resetSlider();
}
});
// Recalculate stuff if view port dimensions have changed this.scope.$watch('rzSliderOptions', function(newValue, oldValue) {
angular.element($window).on('resize', calcDimFn); if (newValue === oldValue)
return;
self.applyOptions();
self.resetSlider();
}, true);
this.initHasRun = true; this.scope.$on('$destroy', function() {
self.unbindEvents();
angular.element($window).off('resize', calcDimFn);
});
},
// Watch for changes to the model /**
* Read the user options and apply them to the slider model
*/
applyOptions: function() {
this.options = RzSliderOptions.getOptions(this.scope.rzSliderOptions);
thrLow = rzThrottle(function() { if (this.options.step <= 0)
self.setMinAndMax(); this.options.step = 1;
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel)); this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
self.updateSelectionBar(); this.options.draggableRange = this.range && this.options.draggableRange;
self.updateTicksScale(); this.options.showTicks = this.options.showTicks || this.options.showTicksValues;
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 (self.range) { /**
self.updateCmbLabel(); * Resets slider
} *
* @returns {undefined}
*/
resetSlider: function() {
this.manageElementsStyle();
this.setMinAndMax();
this.updateCeilLab();
this.updateFloorLab();
this.unbindEvents();
this.manageEventsBindings();
this.setDisabledState();
this.calcViewDimensions();
},
}, self.options.interval); /**
* 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;
}
thrHigh = rzThrottle(function() { }, this);
self.setMinAndMax();
self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
self.updateSelectionBar();
self.updateTicksScale();
self.updateCmbLabel();
}, self.options.interval);
this.scope.$on('rzSliderForceRender', function() { // Initialize offset cache properties
self.resetLabelsValue(); this.selBar.rzsl = 0;
thrLow(); this.minH.rzsl = 0;
if (self.range) { this.maxH.rzsl = 0;
thrHigh(); this.flrLab.rzsl = 0;
} this.ceilLab.rzsl = 0;
self.resetSlider(); this.minLab.rzsl = 0;
}); this.maxLab.rzsl = 0;
this.cmbLab.rzsl = 0;
},
// Watchers /** Update each elements style based on options
this.scope.$watch('rzSliderModel', function(newValue, oldValue) { *
if (newValue === oldValue) */
return; manageElementsStyle: function() {
thrLow();
}); if (!this.range)
this.maxH.css('display', 'none');
else
this.maxH.css('display', null);
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.showTicks)
this.ticks.html('');
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)
},
this.scope.$watch('rzSliderHigh', function(newValue, oldValue) { /**
if (newValue === oldValue) * Manage the events bindings based on readOnly and disabled options
return; *
if (newValue != null) * @returns {undefined}
thrHigh(); */
if (self.range && newValue == null || !self.range && newValue != null) { manageEventsBindings: function() {
self.applyOptions(); if (this.options.disabled || this.options.readOnly)
self.resetSlider(); this.unbindEvents();
} else if (!this.options.disabled || !this.options.readOnly)
}); this.bindEvents();
},
this.scope.$watch('rzSliderOptions', function(newValue, oldValue) { /**
if (newValue === oldValue) * Set the disabled state based on rzSliderDisabled
return; *
self.applyOptions(); * @returns {undefined}
self.resetSlider(); */
}, true); setDisabledState: function() {
if (this.options.disabled) {
this.sliderElem.attr('disabled', 'disabled');
} else {
this.sliderElem.attr('disabled', null);
}
},
this.scope.$on('$destroy', function() { /**
self.unbindEvents(); * Reset label values
angular.element($window).off('resize', calcDimFn); *
}); * @return {undefined}
}, */
resetLabelsValue: function() {
this.minLab.rzsv = undefined;
this.maxLab.rzsv = undefined;
},
/** /**
* Read the user options and apply them to the slider model * Initialize slider handles positions and labels
*/ *
applyOptions: function() { * Run only once during initialization and every time view port changes size
this.options = RzSliderOptions.getOptions(this.scope.rzSliderOptions); *
* @returns {undefined}
if (this.options.step <= 0) */
this.options.step = 1; initHandles: function() {
this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined; this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel));
this.options.draggableRange = this.range && this.options.draggableRange;
this.options.showTicks = this.options.showTicks || this.options.showTicksValues;
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);
};
},
/**
* Resets slider
*
* @returns {undefined}
*/
resetSlider: function() {
this.manageElementsStyle();
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); /*
the order here is important since the selection bar should be
// Initialize offset cache properties updated after the high handle but before the combined label
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;
},
/** Update each elements style based on options
*
*/
manageElementsStyle: function() {
if (!this.range)
this.maxH.css('display', 'none');
else
this.maxH.css('display', null);
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.showTicks)
this.ticks.html('');
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.range)
if (this.options.disabled || this.options.readOnly) this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh));
this.unbindEvents(); this.updateSelectionBar();
else if (!this.options.disabled || !this.options.readOnly) if (this.range)
this.bindEvents(); this.updateCmbLabel();
},
/**
* 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);
}
},
/** this.updateTicksScale();
* 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
* Translate value to human readable format * @param {jqLite} label
* * @param {boolean} [useCustomTr]
* @param {number|string} value * @returns {undefined}
* @param {jqLite} label */
* @param {boolean} [useCustomTr] translateFn: function(value, label, useCustomTr) {
* @returns {undefined} useCustomTr = useCustomTr === undefined ? true : useCustomTr;
*/
translateFn: function(value, label, useCustomTr) {
useCustomTr = useCustomTr === undefined ? true : useCustomTr;
var valStr = String((useCustomTr ? this.customTrFn(value, this.options.id) : value)), var valStr = String((useCustomTr ? this.customTrFn(value, this.options.id) : value)),
getWidth = false; getWidth = false;
if (label.rzsv === undefined || label.rzsv.length !== valStr.length || (label.rzsv.length > 0 && label.rzsw === 0)) { if (label.rzsv === undefined || label.rzsv.length !== valStr.length || (label.rzsv.length > 0 && label.rzsw === 0)) {
getWidth = true; getWidth = true;
label.rzsv = valStr; label.rzsv = valStr;
} }
label.text(valStr); label.text(valStr);
// Update width only when length of the label have changed // Update width only when length of the label have changed
if (getWidth) { if (getWidth) {
this.getWidth(label); this.getWidth(label);
} }
}, },
/** /**
* Set maximum and minimum values for the slider and ensure the model and high * Set maximum and minimum values for the slider and ensure the model and high
* value match these limits * value match these limits
* @returns {undefined} * @returns {undefined}
*/ */
setMinAndMax: function() { setMinAndMax: function() {
this.step = +this.options.step; this.step = +this.options.step;
this.precision = +this.options.precision; this.precision = +this.options.precision;
this.scope.rzSliderModel = this.roundStep(this.scope.rzSliderModel); this.scope.rzSliderModel = this.roundStep(this.scope.rzSliderModel);
if (this.range) if (this.range)
this.scope.rzSliderHigh = this.roundStep(this.scope.rzSliderHigh); this.scope.rzSliderHigh = this.roundStep(this.scope.rzSliderHigh);
this.minValue = this.roundStep(+this.options.floor); this.minValue = this.roundStep(+this.options.floor);
if (this.options.ceil) if (this.options.ceil)
this.maxValue = this.roundStep(+this.options.ceil); this.maxValue = this.roundStep(+this.options.ceil);
else else
this.maxValue = this.options.ceil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel; this.maxValue = this.options.ceil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
this.valueRange = this.maxValue - this.minValue; this.valueRange = this.maxValue - this.minValue;
}, },
/** /**
* Adds accessibility atributes * Adds accessibility atributes
* *
* Run only once during initialization * Run only once during initialization
* *
* @returns {undefined} * @returns {undefined}
*/ */
addAccessibility: function() { addAccessibility: function() {
this.sliderElem.attr("role", "slider"); this.sliderElem.attr("role", "slider");
}, },
/**
* 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); * 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.maxLeft = this.barWidth - handleWidth; this.handleHalfWidth = handleWidth / 2;
this.barWidth = this.getWidth(this.fullBar);
this.getWidth(this.sliderElem); this.maxLeft = this.barWidth - handleWidth;
this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;
if (this.initHasRun) { this.getWidth(this.sliderElem);
this.updateFloorLab(); this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;
this.updateCeilLab();
this.initHandles();
}
},
/** if (this.initHasRun) {
* Update the ticks position this.updateFloorLab();
* this.updateCeilLab();
* @returns {undefined} this.initHandles();
*/ }
updateTicksScale: function() { },
if (!this.options.showTicks) return;
if (!this.step) return; //if step is 0, the following loop will be endless. /**
* Update the ticks position
var positions = '', *
ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1; * @returns {undefined}
for (var i = 0; i < ticksCount; i++) { */
var value = this.roundStep(this.minValue + i * this.step); updateTicksScale: function() {
var selectedClass = this.isTickSelected(value) ? 'selected' : ''; if (!this.options.showTicks) return;
positions += '<li class="tick ' + selectedClass + '">'; if (!this.step) return; //if step is 0, the following loop will be endless.
if (this.options.showTicksValues) {
var tooltip = ''; var positions = '',
if (this.options.ticksValuesTooltip) { ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1;
tooltip = 'uib-tooltip="' + this.options.ticksValuesTooltip(value) + '"'; for (var i = 0; i < ticksCount; i++) {
} var value = this.roundStep(this.minValue + i * this.step);
positions += '<span ' + tooltip + ' class="tick-value">' + this.getDisplayValue(value) + '</span>'; var selectedClass = this.isTickSelected(value) ? 'selected' : '';
positions += '<li class="tick ' + selectedClass + '">';
if (this.options.showTicksValues) {
var tooltip = '';
if (this.options.ticksValuesTooltip) {
tooltip = 'uib-tooltip="' + this.options.ticksValuesTooltip(value) + '"';
} }
positions += '</li>'; positions += '<span ' + tooltip + ' class="tick-value">' + this.getDisplayValue(value) + '</span>';
}
this.ticks.html(positions);
if (this.options.ticksValuesTooltip)
$compile(this.ticks.contents())(this.scope);
},
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.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.minValue, this.flrLab);
this.getWidth(this.flrLab);
},
/**
* Call the onStart callback if defined
*
* @returns {undefined}
*/
callOnStart: function() {
if (this.options.onStart) {
var self = this;
$timeout(function() {
self.options.onStart();
});
} }
}, positions += '</li>';
}
this.ticks.html(positions);
if (this.options.ticksValuesTooltip)
$compile(this.ticks.contents())(this.scope);
},
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;
},
/** /**
* Call the onChange callback if defined * Update position of the ceiling label
* *
* @returns {undefined} * @returns {undefined}
*/ */
callOnChange: function() { updateCeilLab: function() {
if (this.options.onChange) { this.translateFn(this.maxValue, this.ceilLab);
var self = this; this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw);
$timeout(function() { this.getWidth(this.ceilLab);
self.options.onChange(); },
});
}
},
/** /**
* Call the onEnd callback if defined * Update position of the floor label
* *
* @returns {undefined} * @returns {undefined}
*/ */
callOnEnd: function() { updateFloorLab: function() {
if (this.options.onEnd) { this.translateFn(this.minValue, this.flrLab);
var self = this; this.getWidth(this.flrLab);
$timeout(function() { },
self.options.onEnd();
});
}
},
/** /**
* Update slider handles and label positions * Call the onStart callback if defined
* *
* @param {string} which * @returns {undefined}
* @param {number} newOffset */
*/ callOnStart: function() {
updateHandles: function(which, newOffset) { if (this.options.onStart) {
if (which === 'rzSliderModel') { var self = this;
this.updateLowHandle(newOffset); $timeout(function() {
this.updateSelectionBar(); self.options.onStart();
this.updateTicksScale(); });
}
if (this.range) { },
this.updateCmbLabel();
}
return;
}
if (which === 'rzSliderHigh') { /**
this.updateHighHandle(newOffset); * Call the onChange callback if defined
this.updateSelectionBar(); *
this.updateTicksScale(); * @returns {undefined}
*/
callOnChange: function() {
if (this.options.onChange) {
var self = this;
$timeout(function() {
self.options.onChange();
});
}
},
if (this.range) { /**
this.updateCmbLabel(); * Call the onEnd callback if defined
} *
return; * @returns {undefined}
} */
callOnEnd: function() {
if (this.options.onEnd) {
var self = this;
$timeout(function() {
self.options.onEnd();
});
}
},
// Update both /**
* Update slider handles and label positions
*
* @param {string} which
* @param {number} newOffset
*/
updateHandles: function(which, newOffset) {
if (which === 'rzSliderModel') {
this.updateLowHandle(newOffset); this.updateLowHandle(newOffset);
this.updateHighHandle(newOffset);
this.updateSelectionBar(); this.updateSelectionBar();
this.updateTicksScale(); this.updateTicksScale();
this.updateCmbLabel();
},
/**
* Update low slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateLowHandle: function(newOffset) {
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) { if (this.range) {
flHidden = true; this.updateCmbLabel();
this.hideEl(this.flrLab);
} }
else { return;
flHidden = false; }
this.showEl(this.flrLab);
if (which === 'rzSliderHigh') {
this.updateHighHandle(newOffset);
this.updateSelectionBar();
this.updateTicksScale();
if (this.range) {
this.updateCmbLabel();
} }
return;
}
// Update both
this.updateLowHandle(newOffset);
this.updateHighHandle(newOffset);
this.updateSelectionBar();
this.updateTicksScale();
this.updateCmbLabel();
},
/**
* Update low slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateLowHandle: function(newOffset) {
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.minLab.rzsl + this.minLab.rzsw >= this.ceilLab.rzsl - this.handleHalfWidth - 10) { if (this.range) {
clHidden = true; if (this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10) {
this.hideEl(this.ceilLab); this.hideEl(this.ceilLab);
} } else if (!clHidden) {
else {
clHidden = false;
this.showEl(this.ceilLab); this.showEl(this.ceilLab);
} }
if (this.range) { // Hide or show floor label
if (this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10) { if (this.maxLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + this.handleHalfWidth) {
this.hideEl(this.ceilLab); this.hideEl(this.flrLab);
} } else if (!flHidden) {
else if (!clHidden) { this.showEl(this.flrLab);
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 * Update slider selection bar, combined label and range label
* *
* @returns {undefined} * @returns {undefined}
*/ */
updateSelectionBar: function() { updateSelectionBar: function() {
this.setWidth(this.selBar, Math.abs(this.maxH.rzsl - this.minH.rzsl) + this.handleHalfWidth); this.setWidth(this.selBar, Math.abs(this.maxH.rzsl - this.minH.rzsl) + this.handleHalfWidth);
this.setLeft(this.selBar, this.range ? this.minH.rzsl + this.handleHalfWidth : 0); 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) {
lowTr = this.getDisplayValue(this.scope.rzSliderModel);
highTr = this.getDisplayValue(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);
}
},
/** /**
* Return the translated value if a translate function is provided else the original value * Update combined label position and value
* @param value *
* @returns {*} * @returns {undefined}
*/ */
getDisplayValue: function(value) { updateCmbLabel: function() {
return this.customTrFn(value, this.options.id); var lowTr, highTr;
},
if (this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl) {
/** lowTr = this.getDisplayValue(this.scope.rzSliderModel);
* Round value to step and precision highTr = this.getDisplayValue(this.scope.rzSliderHigh);
*
* @param {number} value this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false);
* @returns {number} this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2);
*/ this.hideEl(this.minLab);
roundStep: function(value) { this.hideEl(this.maxLab);
var step = this.step, this.showEl(this.cmbLab);
remainder = +((value - this.minValue) % step).toFixed(3), } else {
steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder; this.showEl(this.maxLab);
this.showEl(this.minLab);
steppedValue = steppedValue.toFixed(this.precision); this.hideEl(this.cmbLab);
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}); /**
}, * 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);
},
/** /**
* Set element left offset * Round value to step and precision
* *
* @param {jqLite} elem The jqLite wrapped DOM element * @param {number} value
* @param {number} left * @returns {number}
* @returns {number} */
*/ roundStep: function(value) {
setLeft: function(elem, left) { var step = this.step,
elem.rzsl = left; remainder = +((value - this.minValue) % step).toFixed(3),
elem.css({left: left + 'px'}); steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;
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) * this.options.scale;
return elem.rzsw;
},
/**
* Set element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} width
* @returns {number}
*/
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 (this.sanitizeOffsetValue(val) - this.minValue) * this.maxLeft / this.valueRange || 0;
},
/**
* Ensure that the position rendered is within the slider bounds, even if the value is not
*
* @param {number} val
* @returns {number}
*/
sanitizeOffsetValue: 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.maxLeft) * this.valueRange + this.minValue;
},
// Events steppedValue = steppedValue.toFixed(this.precision);
return +steppedValue;
},
/** /**
* Get the X-coordinate of an event * Hide element
* *
* @param {Object} event The event * @param element
* @returns {number} * @returns {jqLite} The jqLite wrapped DOM element
*/ */
getEventX: function(event) { hideEl: function(element) {
/* http://stackoverflow.com/a/12336075/282882 */ return element.css({
//noinspection JSLint opacity: 0
if ('clientX' in event) { });
return event.clientX; },
}
return event.originalEvent === undefined ? /**
event.touches[0].clientX * Show element
: event.originalEvent.touches[0].clientX; *
}, * @param element The jqLite wrapped DOM element
* @returns {jqLite} The jqLite
*/
showEl: function(element) {
if (!!element.rzAlwaysHide) {
return element;
}
/** return element.css({
* Get the handle closest to an event. opacity: 1
* });
* @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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth) * this.options.scale;
return Math.abs(offset - this.minH.rzsl) < Math.abs(offset - this.maxH.rzsl) ? this.minH : this.maxH;
},
/**
* Bind mouse and touch events to slider handles
*
* @returns {undefined}
*/
bindEvents: function() {
if (this.options.readOnly || this.options.disabled) return;
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;
}
this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'rzSliderModel')); /**
if (this.range) { * Set element left offset
this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); *
} * @param {jqLite} elem The jqLite wrapped DOM element
this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null)); * @param {number} left
this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar)); * @returns {number}
this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking)); */
this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar)); setLeft: function(elem, left) {
this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null)); elem.rzsl = left;
this.ticks.on('mousedown', angular.bind(this, this.onMove, this.ticks)); elem.css({
left: left + 'px'
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel')); });
if (this.range) { return left;
this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); },
}
this.fullBar.on('touchstart', angular.bind(this, this.onStart, null, null));
this.fullBar.on('touchstart', angular.bind(this, this.onMove, this.fullBar));
this.selBar.on('touchstart', angular.bind(this, barStart, null, barTracking));
this.selBar.on('touchstart', angular.bind(this, barMove, this.selBar));
this.ticks.on('touchstart', angular.bind(this, this.onStart, null, null));
this.ticks.on('touchstart', angular.bind(this, this.onMove, this.ticks));
},
/**
* 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(); * 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) * this.options.scale;
return elem.rzsw;
},
if (this.tracking !== '') { /**
return; * Set element width
} *
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} width
* @returns {number}
*/
setWidth: function(elem, width) {
elem.rzsw = width;
elem.css({
width: width + 'px'
});
return width;
},
// We have to do this in case the HTML where the sliders are on /**
// have been animated into view. * Translate value to pixel offset
this.calcViewDimensions(); *
* @param {number} val
* @returns {number}
*/
valueToOffset: function(val) {
return (this.sanitizeOffsetValue(val) - this.minValue) * this.maxLeft / this.valueRange || 0;
},
if (pointer) { /**
this.tracking = ref; * Ensure that the position rendered is within the slider bounds, even if the value is not
} *
else { * @param {number} val
pointer = this.getNearestHandle(event); * @returns {number}
this.tracking = pointer === this.minH ? 'rzSliderModel' : 'rzSliderHigh'; */
} sanitizeOffsetValue: function(val) {
return Math.min(Math.max(val, this.minValue), this.maxValue);
},
pointer.addClass('rz-active'); /**
* Translate offset to model value
*
* @param {number} offset
* @returns {number}
*/
offsetToValue: function(offset) {
return (offset / this.maxLeft) * this.valueRange + this.minValue;
},
ehMove = angular.bind(this, this.dragging.active ? this.onDragMove : this.onMove, pointer); // Events
ehEnd = angular.bind(this, this.onEnd, ehMove);
$document.on(eventNames.moveEvent, ehMove); /**
$document.one(eventNames.endEvent, ehEnd); * Get the X-coordinate of an event
this.callOnStart(); *
}, * @param {Object} event The event
* @returns {number}
*/
getEventX: function(event) {
/* http://stackoverflow.com/a/12336075/282882 */
//noinspection JSLint
if ('clientX' in event) {
return event.clientX;
}
/** return event.originalEvent === undefined ?
* onMove event handler event.touches[0].clientX : event.originalEvent.touches[0].clientX;
* },
* @param {jqLite} pointer
* @param {Event} event The event
* @returns {undefined}
*/
onMove: function(pointer, event) {
var eventX = this.getEventX(event),
sliderLO, newOffset, newValue;
sliderLO = this.sliderElem.rzsl;
newOffset = (eventX - sliderLO - this.handleHalfWidth) * this.options.scale;
if (newOffset <= 0) {
if (pointer.rzsl === 0)
return;
newValue = this.minValue;
newOffset = 0;
}
else if (newOffset >= this.maxLeft) {
if (pointer.rzsl === this.maxLeft)
return;
newValue = this.maxValue;
newOffset = this.maxLeft;
}
else {
newValue = this.offsetToValue(newOffset);
newValue = this.roundStep(newValue);
newOffset = this.valueToOffset(newValue);
}
this.positionTrackingHandle(newValue, newOffset);
},
/**
* 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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth;
this.dragging = {
active: true,
value: this.offsetToValue(offset),
difference: this.scope.rzSliderHigh - this.scope.rzSliderModel,
offset: offset,
lowDist: offset - this.minH.rzsl,
highDist: this.maxH.rzsl - offset
};
this.minH.addClass('rz-active');
this.maxH.addClass('rz-active');
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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth,
newMinOffset, newMaxOffset,
newMinValue, newMaxValue;
if (newOffset <= this.dragging.lowDist) {
if (pointer.rzsl === this.dragging.lowDist) {
return;
}
newMinValue = this.minValue;
newMinOffset = 0;
newMaxValue = this.minValue + this.dragging.difference;
newMaxOffset = this.valueToOffset(newMaxValue);
}
else if (newOffset >= this.maxLeft - this.dragging.highDist) {
if (pointer.rzsl === this.dragging.highDist) {
return;
}
newMaxValue = this.maxValue;
newMaxOffset = this.maxLeft;
newMinValue = this.maxValue - this.dragging.difference;
newMinOffset = this.valueToOffset(newMinValue);
}
else {
newMinValue = this.offsetToValue(newOffset - this.dragging.lowDist);
newMinValue = this.roundStep(newMinValue);
newMinOffset = this.valueToOffset(newMinValue);
newMaxValue = newMinValue + this.dragging.difference;
newMaxOffset = this.valueToOffset(newMaxValue);
}
this.positionTrackingBar(newMinValue, newMaxValue, newMinOffset, newMaxOffset); /**
}, * 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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth) * this.options.scale;
return Math.abs(offset - this.minH.rzsl) < Math.abs(offset - this.maxH.rzsl) ? this.minH : this.maxH;
},
/** /**
* Set the new value and offset for the entire bar * Bind mouse and touch events to slider handles
* *
* @param {number} newMinValue the new minimum value * @returns {undefined}
* @param {number} newMaxValue the new maximum value */
* @param {number} newMinOffset the new minimum offset bindEvents: function() {
* @param {number} newMaxOffset the new maximum offset if (this.options.readOnly || this.options.disabled) return;
*/ var barTracking, barStart, barMove;
positionTrackingBar: function(newMinValue, newMaxValue, newMinOffset, newMaxOffset) {
this.scope.rzSliderModel = newMinValue; if (this.options.draggableRange) {
this.scope.rzSliderHigh = newMaxValue; barTracking = 'rzSliderDrag';
this.updateHandles('rzSliderModel', newMinOffset); barStart = this.onDragStart;
this.updateHandles('rzSliderHigh', newMaxOffset); barMove = this.onDragMove;
this.scope.$apply(); } else {
this.callOnChange(); barTracking = 'rzSliderModel';
}, barStart = this.onStart;
barMove = this.onMove;
}
/** this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
* Set the new value and offset to the current tracking handle if (this.range) {
* this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
* @param {number} newValue new model value }
* @param {number} newOffset new offset value this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null));
*/ this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar));
positionTrackingHandle: function(newValue, newOffset) { this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking));
if (this.range) { this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar));
/* This is to check if we need to switch the min and max handles*/ this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null));
if (this.tracking === 'rzSliderModel' && newValue >= this.scope.rzSliderHigh) { this.ticks.on('mousedown', angular.bind(this, this.onMove, this.ticks));
this.scope[this.tracking] = this.scope.rzSliderHigh;
this.updateHandles(this.tracking, this.maxH.rzsl); this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
this.tracking = 'rzSliderHigh'; if (this.range) {
this.minH.removeClass('rz-active'); this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
this.maxH.addClass('rz-active'); }
/* We need to apply here because we are not sure that we will enter the next block */ this.fullBar.on('touchstart', angular.bind(this, this.onStart, null, null));
this.scope.$apply(); this.fullBar.on('touchstart', angular.bind(this, this.onMove, this.fullBar));
this.callOnChange(); this.selBar.on('touchstart', angular.bind(this, barStart, null, barTracking));
} this.selBar.on('touchstart', angular.bind(this, barMove, this.selBar));
else if (this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel) { this.ticks.on('touchstart', angular.bind(this, this.onStart, null, null));
this.scope[this.tracking] = this.scope.rzSliderModel; this.ticks.on('touchstart', angular.bind(this, this.onMove, this.ticks));
this.updateHandles(this.tracking, this.minH.rzsl); },
this.tracking = 'rzSliderModel';
this.maxH.removeClass('rz-active'); /**
this.minH.addClass('rz-active'); * Unbind mouse and touch events to slider handles
/* We need to apply here because we are not sure that we will enter the next block */ *
this.scope.$apply(); * @returns {undefined}
this.callOnChange(); */
} 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();
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();
if (pointer) {
this.tracking = ref;
} else {
pointer = this.getNearestHandle(event);
this.tracking = pointer === this.minH ? 'rzSliderModel' : 'rzSliderHigh';
}
pointer.addClass('rz-active');
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 eventX = this.getEventX(event),
sliderLO, newOffset, newValue;
sliderLO = this.sliderElem.rzsl;
newOffset = (eventX - sliderLO - this.handleHalfWidth) * this.options.scale;
if (newOffset <= 0) {
if (pointer.rzsl === 0)
return;
newValue = this.minValue;
newOffset = 0;
} else if (newOffset >= this.maxLeft) {
if (pointer.rzsl === this.maxLeft)
return;
newValue = this.maxValue;
newOffset = this.maxLeft;
} else {
newValue = this.offsetToValue(newOffset);
newValue = this.roundStep(newValue);
newOffset = this.valueToOffset(newValue);
}
this.positionTrackingHandle(newValue, newOffset);
},
/**
* 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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth;
this.dragging = {
active: true,
value: this.offsetToValue(offset),
difference: this.scope.rzSliderHigh - this.scope.rzSliderModel,
offset: offset,
lowDist: offset - this.minH.rzsl,
highDist: this.maxH.rzsl - offset
};
this.minH.addClass('rz-active');
this.maxH.addClass('rz-active');
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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth,
newMinOffset, newMaxOffset,
newMinValue, newMaxValue;
if (newOffset <= this.dragging.lowDist) {
if (pointer.rzsl === this.dragging.lowDist) {
return;
}
newMinValue = this.minValue;
newMinOffset = 0;
newMaxValue = this.minValue + this.dragging.difference;
newMaxOffset = this.valueToOffset(newMaxValue);
} else if (newOffset >= this.maxLeft - this.dragging.highDist) {
if (pointer.rzsl === this.dragging.highDist) {
return;
} }
newMaxValue = this.maxValue;
newMaxOffset = this.maxLeft;
newMinValue = this.maxValue - this.dragging.difference;
newMinOffset = this.valueToOffset(newMinValue);
} else {
newMinValue = this.offsetToValue(newOffset - this.dragging.lowDist);
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.scope.$apply();
this.callOnChange();
},
if (this.scope[this.tracking] !== newValue) { /**
this.scope[this.tracking] = newValue; * Set the new value and offset to the current tracking handle
this.updateHandles(this.tracking, newOffset); *
* @param {number} newValue new model value
* @param {number} newOffset new offset value
*/
positionTrackingHandle: function(newValue, newOffset) {
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) {
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');
/* We need to apply here because we are not sure that we will enter the next block */
this.scope.$apply();
this.callOnChange();
} 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');
/* We need to apply here because we are not sure that we will enter the next block */
this.scope.$apply(); this.scope.$apply();
this.callOnChange(); this.callOnChange();
} }
}, }
/**
* 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'); if (this.scope[this.tracking] !== newValue) {
this.maxH.removeClass('rz-active'); this.scope[this.tracking] = newValue;
this.updateHandles(this.tracking, newOffset);
this.scope.$apply();
this.callOnChange();
}
},
$document.off(moveEventName, ehMove); /**
* 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.scope.$emit('slideEnded'); this.minH.removeClass('rz-active');
this.tracking = ''; this.maxH.removeClass('rz-active');
this.dragging.active = false; $document.off(moveEventName, ehMove);
this.callOnEnd();
},
/** this.scope.$emit('slideEnded');
* Get event names for move and event end this.tracking = '';
*
* @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)) { this.dragging.active = false;
eventNames.moveEvent = 'touchmove'; this.callOnEnd();
eventNames.endEvent = 'touchend'; },
}
else {
eventNames.moveEvent = 'mousemove';
eventNames.endEvent = 'mouseup';
}
return eventNames; /**
* 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 Slider; return eventNames;
}]) }
};
.directive('rzslider', ['RzSlider', function(RzSlider) { return Slider;
'use strict'; }])
return { .directive('rzslider', ['RzSlider', function(RzSlider) {
restrict: 'E', 'use strict';
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) { return {
return new RzSlider(scope, elem); restrict: 'E',
} scope: {
}; rzSliderModel: '=?',
}]); rzSliderHigh: '=?',
rzSliderOptions: '=?',
rzSliderTplUrl: '@'
},
// IDE assist /**
* 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) {
return new RzSlider(scope, elem);
}
};
}]);
// IDE assist
/** /**
* @name ngScope * @name ngScope
......
...@@ -31,53 +31,53 @@ ...@@ -31,53 +31,53 @@
'use strict'; 'use strict';
var module = angular.module('rzModule', []) var module = angular.module('rzModule', [])
.factory('RzSliderOptions', function() { .factory('RzSliderOptions', function() {
var defaultOptions = { var defaultOptions = {
floor: 0, floor: 0,
ceil: null, //defaults to rz-slider-model ceil: null, //defaults to rz-slider-model
step: 1, step: 1,
precision: 0, precision: 0,
id: null, id: null,
translate: null, translate: null,
stepsArray: null, stepsArray: null,
draggableRange: false, draggableRange: false,
showSelectionBar: false, showSelectionBar: false,
hideLimitLabels: false, hideLimitLabels: false,
readOnly: false, readOnly: false,
disabled: false, disabled: false,
interval: 350, interval: 350,
showTicks: false, showTicks: false,
showTicksValues: false, showTicksValues: false,
ticksValuesTooltip: null, ticksValuesTooltip: null,
scale: 1, scale: 1,
onStart: null, onStart: null,
onChange: null, onChange: null,
onEnd: null onEnd: null
}; };
var globalOptions = {}; var globalOptions = {};
var factory = {}; var factory = {};
/** /**
* `options({})` allows global configuration of all sliders in the * `options({})` allows global configuration of all sliders in the
* application. * application.
* *
* var app = angular.module( 'App', ['rzModule'], function( RzSliderOptions ) { * var app = angular.module( 'App', ['rzModule'], function( RzSliderOptions ) {
* // show ticks for all sliders * // show ticks for all sliders
* RzSliderOptions.options( { showTicks: true } ); * RzSliderOptions.options( { showTicks: true } );
* }); * });
*/ */
factory.options = function(value) { factory.options = function(value) {
angular.extend(globalOptions, value); angular.extend(globalOptions, value);
}; };
factory.getOptions = function(options) { factory.getOptions = function(options) {
return angular.extend({}, defaultOptions, globalOptions, options); return angular.extend({}, defaultOptions, globalOptions, options);
}; };
return factory; return factory;
}) })
.value('rzThrottle', .value('rzThrottle',
/** /**
* rzThrottle * rzThrottle
* *
...@@ -124,1242 +124,1238 @@ ...@@ -124,1242 +124,1238 @@
}; };
}) })
.factory('RzSlider', function($timeout, $document, $window, $compile, RzSliderOptions, rzThrottle) { .factory('RzSlider', function($timeout, $document, $window, $compile, RzSliderOptions, rzThrottle) {
'use strict'; '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) {
/** /**
* Slider * The slider's scope
* *
* @param {ngScope} scope The AngularJS scope * @type {ngScope}
* @param {Element} sliderElem The slider directive element wrapped in jqLite
* @constructor
*/ */
var Slider = function(scope, sliderElem) { this.scope = scope;
/**
* The slider's scope
*
* @type {ngScope}
*/
this.scope = scope;
/** /**
* Slider element wrapped in jqLite * Slider element wrapped in jqLite
* *
* @type {jqLite} * @type {jqLite}
*/ */
this.sliderElem = sliderElem; this.sliderElem = sliderElem;
/** /**
* Slider type * Slider type
* *
* @type {boolean} Set to true for range slider * @type {boolean} Set to true for range slider
*/ */
this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined; this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
/** /**
* Values recorded when first dragging the bar * Values recorded when first dragging the bar
* *
* @type {Object} * @type {Object}
*/ */
this.dragging = { this.dragging = {
active: false, active: false,
value: 0, value: 0,
difference: 0, difference: 0,
offset: 0, offset: 0,
lowDist: 0, lowDist: 0,
highDist: 0 highDist: 0
}; };
/** /**
* Half of the width of the slider handles * Half of the width of the slider handles
* *
* @type {number} * @type {number}
*/ */
this.handleHalfWidth = 0; this.handleHalfWidth = 0;
/** /**
* Maximum left the slider handle can have * Maximum left the slider handle can have
* *
* @type {number} * @type {number}
*/ */
this.maxLeft = 0; this.maxLeft = 0;
/** /**
* Precision * Precision
* *
* @type {number} * @type {number}
*/ */
this.precision = 0; this.precision = 0;
/** /**
* Step * Step
* *
* @type {number} * @type {number}
*/ */
this.step = 0; this.step = 0;
/** /**
* The name of the handle we are currently tracking * The name of the handle we are currently tracking
* *
* @type {string} * @type {string}
*/ */
this.tracking = ''; this.tracking = '';
/** /**
* Minimum value (floor) of the model * Minimum value (floor) of the model
* *
* @type {number} * @type {number}
*/ */
this.minValue = 0; this.minValue = 0;
/** /**
* Maximum value (ceiling) of the model * Maximum value (ceiling) of the model
* *
* @type {number} * @type {number}
*/ */
this.maxValue = 0; this.maxValue = 0;
/** /**
* The delta between min and max value * The delta between min and max value
* *
* @type {number} * @type {number}
*/ */
this.valueRange = 0; this.valueRange = 0;
/** /**
* Set to true if init method already executed * Set to true if init method already executed
* *
* @type {boolean} * @type {boolean}
*/ */
this.initHasRun = false; this.initHasRun = false;
// Slider DOM elements wrapped in jqLite // Slider DOM elements wrapped in jqLite
this.fullBar = null; // The whole slider bar this.fullBar = null; // The whole slider bar
this.selBar = null; // Highlight between two handles this.selBar = null; // Highlight between two handles
this.minH = null; // Left slider handle this.minH = null; // Left slider handle
this.maxH = null; // Right slider handle this.maxH = null; // Right slider handle
this.flrLab = null; // Floor label this.flrLab = null; // Floor label
this.ceilLab = null; // Ceiling label this.ceilLab = null; // Ceiling label
this.minLab = null; // Label above the low value this.minLab = null; // Label above the low value
this.maxLab = null; // Label above the high value this.maxLab = null; // Label above the high value
this.cmbLab = null; // Combined label this.cmbLab = null; // Combined label
this.ticks = null; // The ticks this.ticks = null; // The ticks
// Initialize slider // Initialize slider
this.init(); this.init();
}; };
// Add instance methods
Slider.prototype = {
// Add instance methods /**
Slider.prototype = { * Initialize slider
*
* @returns {undefined}
*/
init: function() {
var thrLow, thrHigh,
calcDimFn = angular.bind(this, this.calcViewDimensions),
self = this;
this.applyOptions();
this.initElemHandles();
this.manageElementsStyle();
this.addAccessibility();
this.manageEventsBindings();
this.setDisabledState();
this.calcViewDimensions();
this.setMinAndMax();
$timeout(function() {
self.updateCeilLab();
self.updateFloorLab();
self.initHandles();
self.bindEvents();
});
// 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.setMinAndMax();
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));
self.updateSelectionBar();
self.updateTicksScale();
if (self.range) {
self.updateCmbLabel();
}
/** }, self.options.interval);
* Initialize slider
* thrHigh = rzThrottle(function() {
* @returns {undefined} self.setMinAndMax();
*/ self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
init: function() { self.updateSelectionBar();
var thrLow, thrHigh, self.updateTicksScale();
calcDimFn = angular.bind(this, this.calcViewDimensions), self.updateCmbLabel();
self = this; }, self.options.interval);
this.applyOptions(); this.scope.$on('rzSliderForceRender', function() {
this.initElemHandles(); self.resetLabelsValue();
this.manageElementsStyle(); thrLow();
this.addAccessibility(); if (self.range) {
this.manageEventsBindings(); thrHigh();
this.setDisabledState(); }
this.calcViewDimensions(); self.resetSlider();
this.setMinAndMax(); });
$timeout(function() { // Watchers
self.updateCeilLab(); this.scope.$watch('rzSliderModel', function(newValue, oldValue) {
self.updateFloorLab(); if (newValue === oldValue)
self.initHandles(); return;
self.bindEvents(); thrLow();
}); });
// Recalculate slider view dimensions this.scope.$watch('rzSliderHigh', function(newValue, oldValue) {
this.scope.$on('reCalcViewDimensions', calcDimFn); if (newValue === oldValue)
return;
if (newValue != null)
thrHigh();
if (self.range && newValue == null || !self.range && newValue != null) {
self.applyOptions();
self.resetSlider();
}
});
// Recalculate stuff if view port dimensions have changed this.scope.$watch('rzSliderOptions', function(newValue, oldValue) {
angular.element($window).on('resize', calcDimFn); if (newValue === oldValue)
return;
self.applyOptions();
self.resetSlider();
}, true);
this.initHasRun = true; this.scope.$on('$destroy', function() {
self.unbindEvents();
angular.element($window).off('resize', calcDimFn);
});
},
// Watch for changes to the model /**
* Read the user options and apply them to the slider model
*/
applyOptions: function() {
this.options = RzSliderOptions.getOptions(this.scope.rzSliderOptions);
thrLow = rzThrottle(function() { if (this.options.step <= 0)
self.setMinAndMax(); this.options.step = 1;
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel)); this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
self.updateSelectionBar(); this.options.draggableRange = this.range && this.options.draggableRange;
self.updateTicksScale(); this.options.showTicks = this.options.showTicks || this.options.showTicksValues;
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 (self.range) { /**
self.updateCmbLabel(); * Resets slider
} *
* @returns {undefined}
*/
resetSlider: function() {
this.manageElementsStyle();
this.setMinAndMax();
this.updateCeilLab();
this.updateFloorLab();
this.unbindEvents();
this.manageEventsBindings();
this.setDisabledState();
this.calcViewDimensions();
},
}, self.options.interval); /**
* 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;
}
thrHigh = rzThrottle(function() { }, this);
self.setMinAndMax();
self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
self.updateSelectionBar();
self.updateTicksScale();
self.updateCmbLabel();
}, self.options.interval);
this.scope.$on('rzSliderForceRender', function() { // Initialize offset cache properties
self.resetLabelsValue(); this.selBar.rzsl = 0;
thrLow(); this.minH.rzsl = 0;
if (self.range) { this.maxH.rzsl = 0;
thrHigh(); this.flrLab.rzsl = 0;
} this.ceilLab.rzsl = 0;
self.resetSlider(); this.minLab.rzsl = 0;
}); this.maxLab.rzsl = 0;
this.cmbLab.rzsl = 0;
},
// Watchers /** Update each elements style based on options
this.scope.$watch('rzSliderModel', function(newValue, oldValue) { *
if (newValue === oldValue) */
return; manageElementsStyle: function() {
thrLow();
}); if (!this.range)
this.maxH.css('display', 'none');
else
this.maxH.css('display', null);
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.showTicks)
this.ticks.html('');
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)
},
this.scope.$watch('rzSliderHigh', function(newValue, oldValue) { /**
if (newValue === oldValue) * Manage the events bindings based on readOnly and disabled options
return; *
if (newValue != null) * @returns {undefined}
thrHigh(); */
if (self.range && newValue == null || !self.range && newValue != null) { manageEventsBindings: function() {
self.applyOptions(); if (this.options.disabled || this.options.readOnly)
self.resetSlider(); this.unbindEvents();
} else if (!this.options.disabled || !this.options.readOnly)
}); this.bindEvents();
},
this.scope.$watch('rzSliderOptions', function(newValue, oldValue) { /**
if (newValue === oldValue) * Set the disabled state based on rzSliderDisabled
return; *
self.applyOptions(); * @returns {undefined}
self.resetSlider(); */
}, true); setDisabledState: function() {
if (this.options.disabled) {
this.sliderElem.attr('disabled', 'disabled');
} else {
this.sliderElem.attr('disabled', null);
}
},
this.scope.$on('$destroy', function() { /**
self.unbindEvents(); * Reset label values
angular.element($window).off('resize', calcDimFn); *
}); * @return {undefined}
}, */
resetLabelsValue: function() {
this.minLab.rzsv = undefined;
this.maxLab.rzsv = undefined;
},
/** /**
* Read the user options and apply them to the slider model * Initialize slider handles positions and labels
*/ *
applyOptions: function() { * Run only once during initialization and every time view port changes size
this.options = RzSliderOptions.getOptions(this.scope.rzSliderOptions); *
* @returns {undefined}
if (this.options.step <= 0) */
this.options.step = 1; initHandles: function() {
this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined; this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel));
this.options.draggableRange = this.range && this.options.draggableRange;
this.options.showTicks = this.options.showTicks || this.options.showTicksValues;
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);
};
},
/**
* Resets slider
*
* @returns {undefined}
*/
resetSlider: function() {
this.manageElementsStyle();
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); /*
the order here is important since the selection bar should be
// Initialize offset cache properties updated after the high handle but before the combined label
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;
},
/** Update each elements style based on options
*
*/
manageElementsStyle: function() {
if (!this.range)
this.maxH.css('display', 'none');
else
this.maxH.css('display', null);
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.showTicks)
this.ticks.html('');
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.range)
if (this.options.disabled || this.options.readOnly) this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh));
this.unbindEvents(); this.updateSelectionBar();
else if (!this.options.disabled || !this.options.readOnly) if (this.range)
this.bindEvents(); this.updateCmbLabel();
},
/**
* 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);
}
},
/** this.updateTicksScale();
* 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
* Translate value to human readable format * @param {jqLite} label
* * @param {boolean} [useCustomTr]
* @param {number|string} value * @returns {undefined}
* @param {jqLite} label */
* @param {boolean} [useCustomTr] translateFn: function(value, label, useCustomTr) {
* @returns {undefined} useCustomTr = useCustomTr === undefined ? true : useCustomTr;
*/
translateFn: function(value, label, useCustomTr) {
useCustomTr = useCustomTr === undefined ? true : useCustomTr;
var valStr = String((useCustomTr ? this.customTrFn(value, this.options.id) : value)), var valStr = String((useCustomTr ? this.customTrFn(value, this.options.id) : value)),
getWidth = false; getWidth = false;
if (label.rzsv === undefined || label.rzsv.length !== valStr.length || (label.rzsv.length > 0 && label.rzsw === 0)) { if (label.rzsv === undefined || label.rzsv.length !== valStr.length || (label.rzsv.length > 0 && label.rzsw === 0)) {
getWidth = true; getWidth = true;
label.rzsv = valStr; label.rzsv = valStr;
} }
label.text(valStr); label.text(valStr);
// Update width only when length of the label have changed // Update width only when length of the label have changed
if (getWidth) { if (getWidth) {
this.getWidth(label); this.getWidth(label);
} }
}, },
/** /**
* Set maximum and minimum values for the slider and ensure the model and high * Set maximum and minimum values for the slider and ensure the model and high
* value match these limits * value match these limits
* @returns {undefined} * @returns {undefined}
*/ */
setMinAndMax: function() { setMinAndMax: function() {
this.step = +this.options.step; this.step = +this.options.step;
this.precision = +this.options.precision; this.precision = +this.options.precision;
this.scope.rzSliderModel = this.roundStep(this.scope.rzSliderModel); this.scope.rzSliderModel = this.roundStep(this.scope.rzSliderModel);
if (this.range) if (this.range)
this.scope.rzSliderHigh = this.roundStep(this.scope.rzSliderHigh); this.scope.rzSliderHigh = this.roundStep(this.scope.rzSliderHigh);
this.minValue = this.roundStep(+this.options.floor); this.minValue = this.roundStep(+this.options.floor);
if (this.options.ceil) if (this.options.ceil)
this.maxValue = this.roundStep(+this.options.ceil); this.maxValue = this.roundStep(+this.options.ceil);
else else
this.maxValue = this.options.ceil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel; this.maxValue = this.options.ceil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
this.valueRange = this.maxValue - this.minValue; this.valueRange = this.maxValue - this.minValue;
}, },
/** /**
* Adds accessibility atributes * Adds accessibility atributes
* *
* Run only once during initialization * Run only once during initialization
* *
* @returns {undefined} * @returns {undefined}
*/ */
addAccessibility: function() { addAccessibility: function() {
this.sliderElem.attr("role", "slider"); this.sliderElem.attr("role", "slider");
}, },
/**
* 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); * 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.maxLeft = this.barWidth - handleWidth; this.handleHalfWidth = handleWidth / 2;
this.barWidth = this.getWidth(this.fullBar);
this.getWidth(this.sliderElem); this.maxLeft = this.barWidth - handleWidth;
this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;
if (this.initHasRun) { this.getWidth(this.sliderElem);
this.updateFloorLab(); this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;
this.updateCeilLab();
this.initHandles();
}
},
/** if (this.initHasRun) {
* Update the ticks position this.updateFloorLab();
* this.updateCeilLab();
* @returns {undefined} this.initHandles();
*/ }
updateTicksScale: function() { },
if (!this.options.showTicks) return;
if (!this.step) return; //if step is 0, the following loop will be endless. /**
* Update the ticks position
var positions = '', *
ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1; * @returns {undefined}
for (var i = 0; i < ticksCount; i++) { */
var value = this.roundStep(this.minValue + i * this.step); updateTicksScale: function() {
var selectedClass = this.isTickSelected(value) ? 'selected' : ''; if (!this.options.showTicks) return;
positions += '<li class="tick ' + selectedClass + '">'; if (!this.step) return; //if step is 0, the following loop will be endless.
if (this.options.showTicksValues) {
var tooltip = ''; var positions = '',
if (this.options.ticksValuesTooltip) { ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1;
tooltip = 'uib-tooltip="' + this.options.ticksValuesTooltip(value) + '"'; for (var i = 0; i < ticksCount; i++) {
} var value = this.roundStep(this.minValue + i * this.step);
positions += '<span ' + tooltip + ' class="tick-value">' + this.getDisplayValue(value) + '</span>'; var selectedClass = this.isTickSelected(value) ? 'selected' : '';
positions += '<li class="tick ' + selectedClass + '">';
if (this.options.showTicksValues) {
var tooltip = '';
if (this.options.ticksValuesTooltip) {
tooltip = 'uib-tooltip="' + this.options.ticksValuesTooltip(value) + '"';
} }
positions += '</li>'; positions += '<span ' + tooltip + ' class="tick-value">' + this.getDisplayValue(value) + '</span>';
}
this.ticks.html(positions);
if (this.options.ticksValuesTooltip)
$compile(this.ticks.contents())(this.scope);
},
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.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.minValue, this.flrLab);
this.getWidth(this.flrLab);
},
/**
* Call the onStart callback if defined
*
* @returns {undefined}
*/
callOnStart: function() {
if (this.options.onStart) {
var self = this;
$timeout(function() {
self.options.onStart();
});
} }
}, positions += '</li>';
}
this.ticks.html(positions);
if (this.options.ticksValuesTooltip)
$compile(this.ticks.contents())(this.scope);
},
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;
},
/** /**
* Call the onChange callback if defined * Update position of the ceiling label
* *
* @returns {undefined} * @returns {undefined}
*/ */
callOnChange: function() { updateCeilLab: function() {
if (this.options.onChange) { this.translateFn(this.maxValue, this.ceilLab);
var self = this; this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw);
$timeout(function() { this.getWidth(this.ceilLab);
self.options.onChange(); },
});
}
},
/** /**
* Call the onEnd callback if defined * Update position of the floor label
* *
* @returns {undefined} * @returns {undefined}
*/ */
callOnEnd: function() { updateFloorLab: function() {
if (this.options.onEnd) { this.translateFn(this.minValue, this.flrLab);
var self = this; this.getWidth(this.flrLab);
$timeout(function() { },
self.options.onEnd();
});
}
},
/** /**
* Update slider handles and label positions * Call the onStart callback if defined
* *
* @param {string} which * @returns {undefined}
* @param {number} newOffset */
*/ callOnStart: function() {
updateHandles: function(which, newOffset) { if (this.options.onStart) {
if (which === 'rzSliderModel') { var self = this;
this.updateLowHandle(newOffset); $timeout(function() {
this.updateSelectionBar(); self.options.onStart();
this.updateTicksScale(); });
}
if (this.range) { },
this.updateCmbLabel();
}
return;
}
if (which === 'rzSliderHigh') { /**
this.updateHighHandle(newOffset); * Call the onChange callback if defined
this.updateSelectionBar(); *
this.updateTicksScale(); * @returns {undefined}
*/
callOnChange: function() {
if (this.options.onChange) {
var self = this;
$timeout(function() {
self.options.onChange();
});
}
},
if (this.range) { /**
this.updateCmbLabel(); * Call the onEnd callback if defined
} *
return; * @returns {undefined}
} */
callOnEnd: function() {
if (this.options.onEnd) {
var self = this;
$timeout(function() {
self.options.onEnd();
});
}
},
// Update both /**
* Update slider handles and label positions
*
* @param {string} which
* @param {number} newOffset
*/
updateHandles: function(which, newOffset) {
if (which === 'rzSliderModel') {
this.updateLowHandle(newOffset); this.updateLowHandle(newOffset);
this.updateHighHandle(newOffset);
this.updateSelectionBar(); this.updateSelectionBar();
this.updateTicksScale(); this.updateTicksScale();
this.updateCmbLabel();
},
/** if (this.range) {
* Update low slider handle position and label this.updateCmbLabel();
*
* @param {number} newOffset
* @returns {undefined}
*/
updateLowHandle: function(newOffset) {
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 { return;
flHidden = false; }
this.showEl(this.flrLab);
if (which === 'rzSliderHigh') {
this.updateHighHandle(newOffset);
this.updateSelectionBar();
this.updateTicksScale();
if (this.range) {
this.updateCmbLabel();
} }
return;
}
// Update both
this.updateLowHandle(newOffset);
this.updateHighHandle(newOffset);
this.updateSelectionBar();
this.updateTicksScale();
this.updateCmbLabel();
},
/**
* Update low slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateLowHandle: function(newOffset) {
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) { if (this.minLab.rzsl + this.minLab.rzsw >= this.ceilLab.rzsl - this.handleHalfWidth - 10) {
clHidden = true; 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); this.hideEl(this.ceilLab);
} } else if (!clHidden) {
else {
clHidden = false;
this.showEl(this.ceilLab); this.showEl(this.ceilLab);
} }
if (this.range) { // Hide or show floor label
if (this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10) { if (this.maxLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + this.handleHalfWidth) {
this.hideEl(this.ceilLab); this.hideEl(this.flrLab);
} } else if (!flHidden) {
else if (!clHidden) { this.showEl(this.flrLab);
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 * Update slider selection bar, combined label and range label
* *
* @returns {undefined} * @returns {undefined}
*/ */
updateSelectionBar: function() { updateSelectionBar: function() {
this.setWidth(this.selBar, Math.abs(this.maxH.rzsl - this.minH.rzsl) + this.handleHalfWidth); this.setWidth(this.selBar, Math.abs(this.maxH.rzsl - this.minH.rzsl) + this.handleHalfWidth);
this.setLeft(this.selBar, this.range ? this.minH.rzsl + this.handleHalfWidth : 0); 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) {
lowTr = this.getDisplayValue(this.scope.rzSliderModel);
highTr = this.getDisplayValue(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);
}
},
/** /**
* Return the translated value if a translate function is provided else the original value * Update combined label position and value
* @param value *
* @returns {*} * @returns {undefined}
*/ */
getDisplayValue: function(value) { updateCmbLabel: function() {
return this.customTrFn(value, this.options.id); var lowTr, highTr;
},
if (this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl) {
/** lowTr = this.getDisplayValue(this.scope.rzSliderModel);
* Round value to step and precision highTr = this.getDisplayValue(this.scope.rzSliderHigh);
*
* @param {number} value this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false);
* @returns {number} this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2);
*/ this.hideEl(this.minLab);
roundStep: function(value) { this.hideEl(this.maxLab);
var step = this.step, this.showEl(this.cmbLab);
remainder = +((value - this.minValue) % step).toFixed(3), } else {
steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder; this.showEl(this.maxLab);
this.showEl(this.minLab);
steppedValue = steppedValue.toFixed(this.precision); this.hideEl(this.cmbLab);
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}); /**
}, * 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);
},
/** /**
* Set element left offset * Round value to step and precision
* *
* @param {jqLite} elem The jqLite wrapped DOM element * @param {number} value
* @param {number} left * @returns {number}
* @returns {number} */
*/ roundStep: function(value) {
setLeft: function(elem, left) { var step = this.step,
elem.rzsl = left; remainder = +((value - this.minValue) % step).toFixed(3),
elem.css({left: left + 'px'}); steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;
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) * this.options.scale;
return elem.rzsw;
},
/**
* Set element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} width
* @returns {number}
*/
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 (this.sanitizeOffsetValue(val) - this.minValue) * this.maxLeft / this.valueRange || 0;
},
/**
* Ensure that the position rendered is within the slider bounds, even if the value is not
*
* @param {number} val
* @returns {number}
*/
sanitizeOffsetValue: 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.maxLeft) * this.valueRange + this.minValue;
},
// Events steppedValue = steppedValue.toFixed(this.precision);
return +steppedValue;
},
/** /**
* Get the X-coordinate of an event * Hide element
* *
* @param {Object} event The event * @param element
* @returns {number} * @returns {jqLite} The jqLite wrapped DOM element
*/ */
getEventX: function(event) { hideEl: function(element) {
/* http://stackoverflow.com/a/12336075/282882 */ return element.css({
//noinspection JSLint opacity: 0
if ('clientX' in event) { });
return event.clientX; },
}
return event.originalEvent === undefined ? /**
event.touches[0].clientX * Show element
: event.originalEvent.touches[0].clientX; *
}, * @param element The jqLite wrapped DOM element
* @returns {jqLite} The jqLite
*/
showEl: function(element) {
if (!!element.rzAlwaysHide) {
return element;
}
/** return element.css({
* Get the handle closest to an event. opacity: 1
* });
* @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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth) * this.options.scale;
return Math.abs(offset - this.minH.rzsl) < Math.abs(offset - this.maxH.rzsl) ? this.minH : this.maxH;
},
/**
* Bind mouse and touch events to slider handles
*
* @returns {undefined}
*/
bindEvents: function() {
if (this.options.readOnly || this.options.disabled) return;
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;
}
this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'rzSliderModel')); /**
if (this.range) { * Set element left offset
this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); *
} * @param {jqLite} elem The jqLite wrapped DOM element
this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null)); * @param {number} left
this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar)); * @returns {number}
this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking)); */
this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar)); setLeft: function(elem, left) {
this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null)); elem.rzsl = left;
this.ticks.on('mousedown', angular.bind(this, this.onMove, this.ticks)); elem.css({
left: left + 'px'
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel')); });
if (this.range) { return left;
this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); },
}
this.fullBar.on('touchstart', angular.bind(this, this.onStart, null, null));
this.fullBar.on('touchstart', angular.bind(this, this.onMove, this.fullBar));
this.selBar.on('touchstart', angular.bind(this, barStart, null, barTracking));
this.selBar.on('touchstart', angular.bind(this, barMove, this.selBar));
this.ticks.on('touchstart', angular.bind(this, this.onStart, null, null));
this.ticks.on('touchstart', angular.bind(this, this.onMove, this.ticks));
},
/**
* 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(); * 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) * this.options.scale;
return elem.rzsw;
},
if (this.tracking !== '') { /**
return; * Set element width
} *
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} width
* @returns {number}
*/
setWidth: function(elem, width) {
elem.rzsw = width;
elem.css({
width: width + 'px'
});
return width;
},
// We have to do this in case the HTML where the sliders are on /**
// have been animated into view. * Translate value to pixel offset
this.calcViewDimensions(); *
* @param {number} val
* @returns {number}
*/
valueToOffset: function(val) {
return (this.sanitizeOffsetValue(val) - this.minValue) * this.maxLeft / this.valueRange || 0;
},
if (pointer) { /**
this.tracking = ref; * Ensure that the position rendered is within the slider bounds, even if the value is not
} *
else { * @param {number} val
pointer = this.getNearestHandle(event); * @returns {number}
this.tracking = pointer === this.minH ? 'rzSliderModel' : 'rzSliderHigh'; */
} sanitizeOffsetValue: function(val) {
return Math.min(Math.max(val, this.minValue), this.maxValue);
},
pointer.addClass('rz-active'); /**
* Translate offset to model value
*
* @param {number} offset
* @returns {number}
*/
offsetToValue: function(offset) {
return (offset / this.maxLeft) * this.valueRange + this.minValue;
},
ehMove = angular.bind(this, this.dragging.active ? this.onDragMove : this.onMove, pointer); // Events
ehEnd = angular.bind(this, this.onEnd, ehMove);
$document.on(eventNames.moveEvent, ehMove); /**
$document.one(eventNames.endEvent, ehEnd); * Get the X-coordinate of an event
this.callOnStart(); *
}, * @param {Object} event The event
* @returns {number}
*/
getEventX: function(event) {
/* http://stackoverflow.com/a/12336075/282882 */
//noinspection JSLint
if ('clientX' in event) {
return event.clientX;
}
/** return event.originalEvent === undefined ?
* onMove event handler event.touches[0].clientX : event.originalEvent.touches[0].clientX;
* },
* @param {jqLite} pointer
* @param {Event} event The event
* @returns {undefined}
*/
onMove: function(pointer, event) {
var eventX = this.getEventX(event),
sliderLO, newOffset, newValue;
sliderLO = this.sliderElem.rzsl;
newOffset = (eventX - sliderLO - this.handleHalfWidth) * this.options.scale;
if (newOffset <= 0) {
if (pointer.rzsl === 0)
return;
newValue = this.minValue;
newOffset = 0;
}
else if (newOffset >= this.maxLeft) {
if (pointer.rzsl === this.maxLeft)
return;
newValue = this.maxValue;
newOffset = this.maxLeft;
}
else {
newValue = this.offsetToValue(newOffset);
newValue = this.roundStep(newValue);
newOffset = this.valueToOffset(newValue);
}
this.positionTrackingHandle(newValue, newOffset);
},
/**
* 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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth;
this.dragging = {
active: true,
value: this.offsetToValue(offset),
difference: this.scope.rzSliderHigh - this.scope.rzSliderModel,
offset: offset,
lowDist: offset - this.minH.rzsl,
highDist: this.maxH.rzsl - offset
};
this.minH.addClass('rz-active');
this.maxH.addClass('rz-active');
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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth,
newMinOffset, newMaxOffset,
newMinValue, newMaxValue;
if (newOffset <= this.dragging.lowDist) {
if (pointer.rzsl === this.dragging.lowDist) {
return;
}
newMinValue = this.minValue;
newMinOffset = 0;
newMaxValue = this.minValue + this.dragging.difference;
newMaxOffset = this.valueToOffset(newMaxValue);
}
else if (newOffset >= this.maxLeft - this.dragging.highDist) {
if (pointer.rzsl === this.dragging.highDist) {
return;
}
newMaxValue = this.maxValue;
newMaxOffset = this.maxLeft;
newMinValue = this.maxValue - this.dragging.difference;
newMinOffset = this.valueToOffset(newMinValue);
}
else {
newMinValue = this.offsetToValue(newOffset - this.dragging.lowDist);
newMinValue = this.roundStep(newMinValue);
newMinOffset = this.valueToOffset(newMinValue);
newMaxValue = newMinValue + this.dragging.difference;
newMaxOffset = this.valueToOffset(newMaxValue);
}
this.positionTrackingBar(newMinValue, newMaxValue, newMinOffset, newMaxOffset); /**
}, * 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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth) * this.options.scale;
return Math.abs(offset - this.minH.rzsl) < Math.abs(offset - this.maxH.rzsl) ? this.minH : this.maxH;
},
/** /**
* Set the new value and offset for the entire bar * Bind mouse and touch events to slider handles
* *
* @param {number} newMinValue the new minimum value * @returns {undefined}
* @param {number} newMaxValue the new maximum value */
* @param {number} newMinOffset the new minimum offset bindEvents: function() {
* @param {number} newMaxOffset the new maximum offset if (this.options.readOnly || this.options.disabled) return;
*/ var barTracking, barStart, barMove;
positionTrackingBar: function(newMinValue, newMaxValue, newMinOffset, newMaxOffset) {
this.scope.rzSliderModel = newMinValue; if (this.options.draggableRange) {
this.scope.rzSliderHigh = newMaxValue; barTracking = 'rzSliderDrag';
this.updateHandles('rzSliderModel', newMinOffset); barStart = this.onDragStart;
this.updateHandles('rzSliderHigh', newMaxOffset); barMove = this.onDragMove;
this.scope.$apply(); } else {
this.callOnChange(); barTracking = 'rzSliderModel';
}, barStart = this.onStart;
barMove = this.onMove;
}
/** this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
* Set the new value and offset to the current tracking handle if (this.range) {
* this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
* @param {number} newValue new model value }
* @param {number} newOffset new offset value this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null));
*/ this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar));
positionTrackingHandle: function(newValue, newOffset) { this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking));
if (this.range) { this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar));
/* This is to check if we need to switch the min and max handles*/ this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null));
if (this.tracking === 'rzSliderModel' && newValue >= this.scope.rzSliderHigh) { this.ticks.on('mousedown', angular.bind(this, this.onMove, this.ticks));
this.scope[this.tracking] = this.scope.rzSliderHigh;
this.updateHandles(this.tracking, this.maxH.rzsl); this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
this.tracking = 'rzSliderHigh'; if (this.range) {
this.minH.removeClass('rz-active'); this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
this.maxH.addClass('rz-active'); }
/* We need to apply here because we are not sure that we will enter the next block */ this.fullBar.on('touchstart', angular.bind(this, this.onStart, null, null));
this.scope.$apply(); this.fullBar.on('touchstart', angular.bind(this, this.onMove, this.fullBar));
this.callOnChange(); this.selBar.on('touchstart', angular.bind(this, barStart, null, barTracking));
} this.selBar.on('touchstart', angular.bind(this, barMove, this.selBar));
else if (this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel) { this.ticks.on('touchstart', angular.bind(this, this.onStart, null, null));
this.scope[this.tracking] = this.scope.rzSliderModel; this.ticks.on('touchstart', angular.bind(this, this.onMove, this.ticks));
this.updateHandles(this.tracking, this.minH.rzsl); },
this.tracking = 'rzSliderModel';
this.maxH.removeClass('rz-active'); /**
this.minH.addClass('rz-active'); * Unbind mouse and touch events to slider handles
/* We need to apply here because we are not sure that we will enter the next block */ *
this.scope.$apply(); * @returns {undefined}
this.callOnChange(); */
} 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();
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();
if (pointer) {
this.tracking = ref;
} else {
pointer = this.getNearestHandle(event);
this.tracking = pointer === this.minH ? 'rzSliderModel' : 'rzSliderHigh';
}
pointer.addClass('rz-active');
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 eventX = this.getEventX(event),
sliderLO, newOffset, newValue;
sliderLO = this.sliderElem.rzsl;
newOffset = (eventX - sliderLO - this.handleHalfWidth) * this.options.scale;
if (newOffset <= 0) {
if (pointer.rzsl === 0)
return;
newValue = this.minValue;
newOffset = 0;
} else if (newOffset >= this.maxLeft) {
if (pointer.rzsl === this.maxLeft)
return;
newValue = this.maxValue;
newOffset = this.maxLeft;
} else {
newValue = this.offsetToValue(newOffset);
newValue = this.roundStep(newValue);
newOffset = this.valueToOffset(newValue);
}
this.positionTrackingHandle(newValue, newOffset);
},
/**
* 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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth;
this.dragging = {
active: true,
value: this.offsetToValue(offset),
difference: this.scope.rzSliderHigh - this.scope.rzSliderModel,
offset: offset,
lowDist: offset - this.minH.rzsl,
highDist: this.maxH.rzsl - offset
};
this.minH.addClass('rz-active');
this.maxH.addClass('rz-active');
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.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth,
newMinOffset, newMaxOffset,
newMinValue, newMaxValue;
if (newOffset <= this.dragging.lowDist) {
if (pointer.rzsl === this.dragging.lowDist) {
return;
}
newMinValue = this.minValue;
newMinOffset = 0;
newMaxValue = this.minValue + this.dragging.difference;
newMaxOffset = this.valueToOffset(newMaxValue);
} else if (newOffset >= this.maxLeft - this.dragging.highDist) {
if (pointer.rzsl === this.dragging.highDist) {
return;
} }
newMaxValue = this.maxValue;
newMaxOffset = this.maxLeft;
newMinValue = this.maxValue - this.dragging.difference;
newMinOffset = this.valueToOffset(newMinValue);
} else {
newMinValue = this.offsetToValue(newOffset - this.dragging.lowDist);
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.scope.$apply();
this.callOnChange();
},
if (this.scope[this.tracking] !== newValue) { /**
this.scope[this.tracking] = newValue; * Set the new value and offset to the current tracking handle
this.updateHandles(this.tracking, newOffset); *
* @param {number} newValue new model value
* @param {number} newOffset new offset value
*/
positionTrackingHandle: function(newValue, newOffset) {
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) {
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');
/* We need to apply here because we are not sure that we will enter the next block */
this.scope.$apply();
this.callOnChange();
} 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');
/* We need to apply here because we are not sure that we will enter the next block */
this.scope.$apply(); this.scope.$apply();
this.callOnChange(); this.callOnChange();
} }
}, }
/**
* 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'); if (this.scope[this.tracking] !== newValue) {
this.maxH.removeClass('rz-active'); this.scope[this.tracking] = newValue;
this.updateHandles(this.tracking, newOffset);
this.scope.$apply();
this.callOnChange();
}
},
$document.off(moveEventName, ehMove); /**
* 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.scope.$emit('slideEnded'); this.minH.removeClass('rz-active');
this.tracking = ''; this.maxH.removeClass('rz-active');
this.dragging.active = false; $document.off(moveEventName, ehMove);
this.callOnEnd();
},
/** this.scope.$emit('slideEnded');
* Get event names for move and event end this.tracking = '';
*
* @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)) { this.dragging.active = false;
eventNames.moveEvent = 'touchmove'; this.callOnEnd();
eventNames.endEvent = 'touchend'; },
}
else {
eventNames.moveEvent = 'mousemove';
eventNames.endEvent = 'mouseup';
}
return eventNames; /**
* 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 Slider; return eventNames;
}) }
};
.directive('rzslider', function(RzSlider) { return Slider;
'use strict'; })
return { .directive('rzslider', function(RzSlider) {
restrict: 'E', 'use strict';
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) { return {
return new RzSlider(scope, elem); restrict: 'E',
} scope: {
}; rzSliderModel: '=?',
}); rzSliderHigh: '=?',
rzSliderOptions: '=?',
rzSliderTplUrl: '@'
},
// IDE assist /**
* 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) {
return new RzSlider(scope, elem);
}
};
});
// IDE assist
/** /**
* @name ngScope * @name ngScope
......
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