Commit 2c3ca884 authored by Valentin Hervieu's avatar Valentin Hervieu

Merge pull request #158 from rzajac/2.0.0

Refactor to add an rzSliderOptions attribute
parents d26c0eaf 51cd1bfb
# 2.0.0 (2015-11-XX)
## Breaking changes
- All attributes except `rzSliderModel` and `rzSliderHigh` are moved to `rzSliderOptions`. (See the new documentation in ReadMe)
## Features
- Add a `rzSliderOptions` attribute to pass options to the slider.
- Add a `RzSliderOptions.options()` method to set global options.
- Add a `scale` option to fix sliders displayed in an element that uses `transform: scale(0.5)`.
- Add a `stepsArray` option (#163)
- Add an `id` option that is passed to the translate function as second arg (#161)
- Add a `ticksValuesTooltip` option that is used to display a tooltip on the ticks values (requires angular-ui bootstrap).
# 1.1.0 (2015-11-07) # 1.1.0 (2015-11-07)
## Features ## Features
- Configurable update interval (#153) - Configurable update interval (#153)
......
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']);
}; };
## AngularJS slider directive with no external dependencies ## AngularJS slider directive with no external dependencies
> **A refactoring is on-going, any feedback is welcome:** https://github.com/rzajac/angularjs-slider/pull/158
Slider directive implementation for AngularJS, without any dependencies. Slider directive implementation for AngularJS, without any dependencies.
- Mobile friendly - Mobile friendly
...@@ -14,7 +12,6 @@ Slider directive implementation for AngularJS, without any dependencies. ...@@ -14,7 +12,6 @@ Slider directive implementation for AngularJS, without any dependencies.
## Examples ## Examples
- **Various examples:** [http://rzajac.github.io/angularjs-slider/](http://rzajac.github.io/angularjs-slider/index.html) - **Various examples:** [http://rzajac.github.io/angularjs-slider/](http://rzajac.github.io/angularjs-slider/index.html)
- **Slider inside Angular UI tabs:** http://jsfiddle.net/7w755fLv/
## Reporting issues ## Reporting issues
Make sure the report is accompanied by a reproducible demo. The ideal demo is created by forking [our standard jsFiddle](http://jsfiddle.net/1ruqgnhk/), adding your own code and stripping it down to an absolute minimum needed to demonstrate the bug. Make sure the report is accompanied by a reproducible demo. The ideal demo is created by forking [our standard jsFiddle](http://jsfiddle.net/1ruqgnhk/), adding your own code and stripping it down to an absolute minimum needed to demonstrate the bug.
...@@ -33,7 +30,7 @@ $ bower install --save angularjs-slider ...@@ -33,7 +30,7 @@ $ bower install --save angularjs-slider
### Module ### Module
```javascript ```javascript
angular.module('', ['rzModule']); angular.module('yourApp', ['rzModule']);
``` ```
### Single slider ### Single slider
...@@ -49,42 +46,56 @@ $scope.priceSlider = 150; ...@@ -49,42 +46,56 @@ $scope.priceSlider = 150;
</div> </div>
``` ```
Above example would render a slider from 0 to 150. If you need floor and ceiling values use `rz-slider-floor` and `rz-slider-ceil` attributes. Above example would render a slider from 0 to 150. If you need floor and ceiling values use `rz-slider-options` attribute and provide an object with `floor`and `ceil`.
```html ```html
<div> <div>
<rzslider <rzslider
rz-slider-model="priceSlider" rz-slider-model="slider.value"
rz-slider-ceil="450"></rzslider> rz-slider-options="slider.options"></rzslider>
</div>
<!-- OR --> ```
```js
$scope.slider = {
value: 150,
options: {
floor: 0,
ceil: 450
}
};
```
If you don't want to bother with an object set in your javascript file, you can pass an anonymous object literal to the slider options:
```html
<div>
<rzslider <rzslider
rz-slider-model="priceSlider" rz-slider-model="value"
rz-slider-floor="0" rz-slider-options="{floor: 0, ceil: 450}"></rzslider>
rz-slider-ceil="450"></rzslider>
</div> </div>
``` ```
```js
$scope.value = 150;
```
### Range slider ### Range slider
```javascript ```javascript
// In your controller // In your controller
$scope.priceSlider = { $scope.slider = {
min: 100, min: 100,
max: 180, max: 180,
ceil: 500, options: {
floor: 0 floor: 0,
ceil: 450
}
}; };
``` ```
```html ```html
<rzslider <rzslider
rz-slider-floor="priceSlider.floor" rz-slider-model="slider.min"
rz-slider-ceil="priceSlider.ceil" rz-slider-high="slider.max"
rz-slider-model="priceSlider.min" rz-slider-options="slider.options"></rzslider>
rz-slider-high="priceSlider.max"></rzslider>
``` ```
## Directive attributes ## Directive attributes
...@@ -95,115 +106,117 @@ $scope.priceSlider = { ...@@ -95,115 +106,117 @@ $scope.priceSlider = {
**rz-slider-high** **rz-slider-high**
> Model for high value slider. Providing both _rz-slider-high_ and _rz-slider-model_ will render range slider. > Model for high value slider. Providing both _rz-slider-model_ and _rz-slider-high_ will render range slider.
**rz-slider-floor**
> Minimum value for a slider.
**rz-slider-ceil**
> Maximum value for a slider.
**rz-slider-step**
> slider step.
**rz-slider-precision**
> The precision to display values with. The `toFixed()` is used internally for this.
**rz-slider-hide-limit-labels**
> Set to true to hide min / max labels
**rz-slider-always-show-bar**
> Set to true to always show selection bar **rz-slider-options**
**rz-slider-present-only** > An object with all the other options of the slider. Each option can be updated at runtime and the slider will automatically be re-rendered.
> When set to true slider is used in presentation mode. No handle dragging. The default options are:
```js
{
floor: 0,
ceil: null, //defaults to rz-slider-model
step: 1,
precision: 0,
translate: null,
id: null,
stepsArray: null,
draggableRange: false,
showSelectionBar: false,
hideLimitLabels: false,
readOnly: false,
disabled: false,
interval: 350,
showTicks: false,
showTicksValues: false,
scale: 1,
onStart: null,
onChange: null,
onEnd: null
}
````
**rz-slider-draggable-range** **floor** - _Number (defaults to 0)_: Minimum value for a slider.
> When set to true and using a range slider, the range can be dragged by the selection bar. **ceil** - _Number (defaults to `rz-slider-model`value)_: Maximum value for a slider.
**rz-slider-translate** **step** - _Number (defaults to 1)_: Step between each value.
> Custom translate function. Use this if you want to translate values displayed on the slider. For example if you want to display dollar amounts instead of just numbers do this: **precision** - _Number (defaults to 0)_: The precision to display values with. The `toFixed()` is used internally for this.
**rz-slider-on-start** **translate** - _Function(value, sliderId)_: Custom translate function. Use this if you want to translate values displayed on the slider. For example if you want to display dollar amounts instead of just numbers:
```html
<div>
<rzslider
rz-slider-model="slider.value"
rz-slider-options="slider.options"></rzslider>
</div>
```
```js
$scope.slider = {
value: 0,
options: {
floor: 0,
ceil: 100,
translate: function(value) {
return '$' + value;
}
}
};
```
> Function to be called when a slider update is started. **id** - _Any (defaults to null)_: If you want to use the same `translate` function for several sliders, just set the `id` to anything you want, and it will be passed to the `translate(value, sliderId)` function as a second argument.
**rz-slider-on-change** **stepsArray** - _Array_: If you want to display a slider with non linear/number steps. Just pass an array with each slider value and that's it; the floor, ceil and step settings of the slider will be computed automatically. The `rz-slider-model` value will be the index of the selected item in the stepsArray.
> Function to be called when rz-slider-model or rz-slider-high change. **draggableRange** - _Boolean (defaults to false)_: When set to true and using a range slider, the range can be dragged by the selection bar. _This doesn't work when ticks are shown._
**rz-slider-on-end** **showSelectionBar** - _Boolean (defaults to false)_: Set to true to always show the selection bar.
> Function to be called when a slider update is ended. **hideLimitLabels** - _Boolean (defaults to false)_: Set to true to hide min / max labels
**rz-slider-show-ticks** **readOnly** - _Boolean (defaults to false)_: Set to true to make the slider read-only.
> Display a tick for each value. **disabled** - _Boolean (defaults to false)_: Set to true to disable the slider.
**rz-slider-show-ticks-value** **interval** - _Number in ms (defaults to 350)_: Internally, a `throttle` function (See http://underscorejs.org/#throttle) is used when the model or high values of the slider are changed from outside the slider. This is to prevent from re-rendering the slider too many times in a row. `interval` is the number of milliseconds to wait between two updates of the slider.
> Display a tick for each value and the value of the tick. **showTicks** - _Boolean (defaults to false)_: Set to true to display a tick for each step of the slider.
**rz-slider-disabled** **showTicksValues** - _Boolean (defaults to false)_: Set to true to display a tick and the step value for each step of the slider.
> Disable the slider (apply a special style and unbind events) **ticksValuesTooltip** - _Function(value) (defaults to null)_: (requires angular-ui bootstrap) Used to display a tooltip when a tick value is hovered. Set to a function that returns the tooltip content for a given value.
**rz-slider-interval** **scale** - _Number (defaults to 1)_: If you display the slider in an element that uses `transform: scale(0.5)`, set the `scale` value to 2 so that the slider is rendered properly and the events are handled correctly.
> The interval (in ms) at which the slider DOM element updates when rz-slider-model or rz-slider-high change from outside the slider. Defaults to 350. **onStart** - _Function()_: Function to be called when a slider update is started.
```javascript **onChange** - _Function()_: Function to be called when rz-slider-model or rz-slider-high change.
// In your controller
$scope.priceSlider = { **onEnd** - _Function()_: Function to be called when a slider update is ended.
min: 100,
max: 180,
ceil: 500,
floor: 0
};
$scope.translate = function(value)
{
return '$' + value;
}
$scope.onSliderChange = function()
{
console.log('changed', $scope.priceSlider);
}
```
```html ## Change default options
<rzslider If you want the change the default options for all the sliders displayed in your application, you can set them using the `RzSliderOptions.options()` method:
rz-slider-floor="priceSlider.floor" ```js
rz-slider-ceil="priceSlider.ceil" angular.module('App', ['rzModule'])
rz-slider-model="priceSlider.min" .run(function( RzSliderOptions ) {
rz-slider-high="priceSlider.max" // show ticks for all sliders
rz-slider-translate="translate" RzSliderOptions.options( { showTicks: true } );
rz-slider-on-change="onSliderChange()" });
rz-slider-show-ticks="true"></rzslider>
``` ```
## Slider events ## Slider events
To force slider to recalculate dimensions broadcast **reCalcViewDimensions** event from parent scope. This is useful for example when you use slider inside a widget where the content is hidden at start - see the "Sliders into modal" example [on the demo site](http://rzajac.github.io/angularjs-slider/). To force slider to recalculate dimensions, broadcast **reCalcViewDimensions** event from parent scope. This is useful for example when you use slider inside a widget where the content is hidden at start - see the "Sliders into modal" example [on the demo site](http://rzajac.github.io/angularjs-slider/).
You can also force redraw with **rzSliderForceRender** event. You can also force redraw with **rzSliderForceRender** event.
At the end of each "slide" slider emits `slideEnded` event. At the end of each "slide" slider emits `slideEnded` event.
```javascript ```javascript
$scope.$on("slideEnded", function() { $scope.$on("slideEnded", function() {
// user finished sliding a handle // user finished sliding a handle
}); });
``` ```
...@@ -220,13 +233,12 @@ $scope.$on("slideEnded", function() { ...@@ -220,13 +233,12 @@ $scope.$on("slideEnded", function() {
## Browser support ## Browser support
I use Slider on couple of my projects and it's being tested on desktop versions of Chrome, Firefox, Safari, IE 9/10. I use Slider on couple of my projects and it's being tested on desktop versions of Chrome, Firefox, Safari, IE 9/10 (Ticks are displayed using flex display so they don't work on IE9).
Slider is also tested on Android and iPhone using all browsers available on those platforms. Slider is also tested on Android and iPhone using all browsers available on those platforms.
## Disclaimer ## Disclaimer
This project is based on [https://github.com/prajwalkman/angular-slider](https://github.com/prajwalkman/angular-slider). It has been rewritten from scratch in JavaScript This project is based on [https://github.com/prajwalkman/angular-slider](https://github.com/prajwalkman/angular-slider). It has been rewritten from scratch in JavaScript (the original source was written in CoffeeScript).
(the original source was written in CoffeeScript).
## License ## License
......
{ {
"name": "angularjs-slider", "name": "angularjs-slider",
"version": "1.1.0", "version": "2.0.0",
"homepage": "https://github.com/rzajac/angularjs-slider", "homepage": "https://github.com/rzajac/angularjs-slider",
"authors": [ "authors": [
"Rafal Zajac <rzajac@gmail.com>", "Rafal Zajac <rzajac@gmail.com>",
......
* { margin: 0; padding: 0; } * { margin: 0; padding: 0; }
body { font-family: 'Open Sans', sans-serif; color: #1f2636; font-size: 14px; } body { font-family: 'Open Sans', sans-serif; color: #1f2636; font-size: 14px; padding-bottom: 40px; }
header { background: #0db9f0; color: #fff; margin: -40px; margin-bottom: 40px; text-align: center; padding: 40px 0; } header { background: #0db9f0; color: #fff; margin: -40px; margin-bottom: 40px; text-align: center; padding: 40px 0; }
h1 { font-weight: 300; } h1 { font-weight: 300; }
h2 {margin-bottom:10px;}
.wrapper { background: #fff; padding: 40px; } .wrapper { background: #fff; padding: 40px; }
article { margin-bottom: 40px; } article { margin-bottom: 10px; }
.tab-pane{
padding-top: 10px;
}
.field-title {
width: 100px;
}
var app = angular.module('rzSliderDemo', ['rzModule', 'ui.bootstrap']);
app.controller('MainCtrl', function($scope, $rootScope, $timeout, $modal) {
//Minimal slider config
$scope.minSlider = {
value: 10
};
//Slider with selection bar
$scope.slider_visible_bar = {
value: 10,
options: {
showSelectionBar: true
}
};
//Range slider config
$scope.minRangeSlider = {
minValue: 10,
maxValue: 90,
options: {
floor: 0,
ceil: 100,
step: 1
}
};
//Slider config with floor, ceil and step
$scope.slider_floor_ceil = {
value: 12,
options: {
floor: 10,
ceil: 100,
step: 5
}
};
//Slider config with callbacks
$scope.slider_callbacks = {
value: 100,
options: {
onStart: function() {
$scope.otherData.start = $scope.slider_callbacks.value * 10;
},
onChange: function() {
$scope.otherData.change = $scope.slider_callbacks.value * 10;
},
onEnd: function() {
$scope.otherData.end = $scope.slider_callbacks.value * 10;
}
}
};
$scope.otherData = {start: 0, change: 0, end: 0};
//Slider config with custom display function
$scope.slider_translate = {
minValue: 100,
maxValue: 400,
options: {
ceil: 500,
floor: 0,
translate: function(value) {
return '$' + value;
}
}
};
//Slider config with steps array of letters
$scope.slider_alphabet = {
value: 0,
options: {
stepsArray:'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
}
};
//Slider with ticks
$scope.slider_ticks = {
value: 5,
options: {
ceil: 10,
floor: 0,
showTicks: true
}
};
//Slider with ticks and values
$scope.slider_ticks_values = {
value: 5,
options: {
ceil: 10,
floor: 0,
showTicksValues: true,
ticksValuesTooltip: function(v) {
return 'Tooltip for ' + v;
}
}
};
//Range slider with ticks and values
$scope.range_slider_ticks_values = {
minValue: 1,
maxValue: 8,
options: {
ceil: 10,
floor: 0,
showTicksValues: true
}
};
//Slider with draggable range
$scope.slider_draggable_range = {
minValue: 1,
maxValue: 8,
options: {
ceil: 10,
floor: 0,
draggableRange: true
}
};
//Read-only slider
$scope.read_only_slider = {
value: 50,
options: {
ceil: 100,
floor: 0,
readOnly: true
}
};
//Disabled slider
$scope.disabled_slider = {
value: 50,
options: {
ceil: 100,
floor: 0,
disabled: true
}
};
// Slider inside ng-show
$scope.visible = false;
$scope.slider_toggle = {
value: 5,
options: {
ceil: 10,
floor: 0
}
};
$scope.toggle = function() {
$scope.visible = !$scope.visible;
$timeout(function() {
$scope.$broadcast('rzSliderForceRender');
});
};
//Slider inside modal
$scope.percentages = {
normal: {
low: 15
},
range: {
low: 10,
high: 50
}
};
$scope.openModal = function() {
var modalInstance = $modal.open({
templateUrl: 'sliderModal.html',
controller: function($scope, $modalInstance, values) {
$scope.percentages = JSON.parse(JSON.stringify(values)); //Copy of the object in order to keep original values in $scope.percentages in parent controller.
var formatToPercentage = function(value) {
return value + '%';
};
$scope.percentages.normal.options = {
floor: 0,
ceil: 100,
translate: formatToPercentage,
showSelectionBar: true
};
$scope.percentages.range.options = {
floor: 0,
ceil: 100,
translate: formatToPercentage
};
$scope.ok = function() {
$modalInstance.close($scope.percentages);
};
$scope.cancel = function() {
$modalInstance.dismiss();
};
},
resolve: {
values: function() {
return $scope.percentages;
}
}
});
modalInstance.result.then(function(percentages) {
$scope.percentages = percentages;
});
modalInstance.rendered.then(function() {
$rootScope.$broadcast('rzSliderForceRender');//Force refresh sliders on render. Otherwise bullets are aligned at left side.
});
};
//Slider inside tabs
$scope.tabSliders = {
slider1: {
value: 100
},
slider2: {
value: 200
}
};
$scope.refreshSlider = function() {
$timeout(function() {
$scope.$broadcast('rzSliderForceRender');
});
};
//Slider with draggable range
$scope.slider_all_options = {
minValue: 2,
options: {
floor: 0,
ceil: 10,
step: 1,
precision: 0,
draggableRange: false,
showSelectionBar: false,
hideLimitLabels: false,
readOnly: false,
disabled: false,
showTicks: false,
showTicksValues: false
}
};
$scope.toggleHighValue = function() {
if($scope.slider_all_options.maxValue != null) {
$scope.slider_all_options.maxValue = undefined;
}
else {
$scope.slider_all_options.maxValue = 8;
}
}
});
...@@ -2,240 +2,205 @@ ...@@ -2,240 +2,205 @@
<html ng-app="rzSliderDemo"> <html ng-app="rzSliderDemo">
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>AngularJS Touch Slider</title> <title>AngularJS Touch Slider</title>
<link rel="stylesheet" href="demo.css"/> <!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="../dist/rzslider.css"/> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400,700' rel='stylesheet' type='text/css'> <link rel="stylesheet" href="../dist/rzslider.css"/>
<link rel="stylesheet" href="demo.css"/>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400,700' rel='stylesheet' type='text/css'>
</head> </head>
<body ng-controller="MainCtrl"> <body ng-controller="MainCtrl">
<div class="wrapper"> <div class="wrapper">
<header> <header>
<h1>AngularJS Touch Slider</h1> <h1>AngularJS Touch Slider</h1>
</header> </header>
<article> <article>
<h2>Min/max slider example</h2> <h2>Simple slider</h2>
Value: Model: <input type="number" ng-model="minSlider.value"/><br/>
<pre>{{ slider_data | json }}</pre> <rzslider rz-slider-model="minSlider.value"></rzslider>
<p>Value linked on change: {{ otherData.value }}</p> </article>
<rzslider <article>
rz-slider-floor="0.5" <h2>Range slider</h2>
rz-slider-ceil="10.5" Min Value: <input type="number" ng-model="minRangeSlider.minValue"/><br/>
rz-slider-step="0.3" Max Value: <input type="number" ng-model="minRangeSlider.maxValue"/><br/>
rz-slider-precision="1" <rzslider
rz-slider-model="slider_data.value" rz-slider-model="minRangeSlider.minValue"
rz-slider-on-start="onStart()" rz-slider-high="minRangeSlider.maxValue"
rz-slider-on-change="onChange()" rz-slider-options="minRangeSlider.options"
rz-slider-on-end="onEnd()"></rzslider> ></rzslider>
</article> </article>
<article> <article>
<h2>Min/max slider example</h2> <h2>Slider with visible selection bar</h2>
Value: <rzslider
<pre>{{ priceSlider | json }}</pre> rz-slider-model="slider_visible_bar.value"
rz-slider-options="slider_visible_bar.options"
<input type="text" ng-model="priceSlider.min"/><br/> ></rzslider>
<input type="text" ng-model="priceSlider.max"/><br/> </article>
<rzslider <article>
rz-slider-floor="priceSlider.floor" <h2>Slider with custom floor/ceil/step</h2>
rz-slider-ceil="priceSlider.ceil" <rzslider
rz-slider-model="priceSlider.min" rz-slider-model="slider_floor_ceil.value"
rz-slider-high="priceSlider.max" rz-slider-options="slider_floor_ceil.options"
rz-slider-step="6" ></rzslider>
rz-slider-tpl-url="rzSliderTpl.html"></rzslider> </article>
</article>
<article>
<article> <h2>Slider with callbacks on start, change and end</h2>
<h2>Currency slider example</h2> <p>Value linked on start: {{ otherData.start }}</p>
<p>Value linked on change: {{ otherData.change }}</p>
Value: {{ priceSlider2 | json }} <p>Value linked on end: {{ otherData.end }}</p>
<rzslider
rz-slider-floor="0" <rzslider
rz-slider-ceil="450" rz-slider-model="slider_callbacks.value"
rz-slider-model="priceSlider2" rz-slider-options="slider_callbacks.options"
rz-slider-translate="translate" ></rzslider>
rz-slider-tpl-url="rzSliderTpl.html"></rzslider> </article>
</article>
<article>
<article> <h2>Slider with custom display function</h2>
<h2>One value slider example</h2> <rzslider
rz-slider-model="slider_translate.minValue"
Value: {{ priceSlider3 | json }} rz-slider-high="slider_translate.maxValue"
<rzslider rz-slider-model="priceSlider3" rz-slider-options="slider_translate.options"
rz-slider-floor="50" ></rzslider>
rz-slider-ceil="450" </article>
rz-slider-always-show-bar="true"
rz-slider-tpl-url="rzSliderTpl.html"></rzslider> <article>
</article> <h2>Slider with Alphabet</h2>
<rzslider
<article> rz-slider-model="slider_alphabet.value"
<h2>Alphabet slider example</h2> rz-slider-options="slider_alphabet.options"
Value: {{ alphabetTranslate(letter) }} ></rzslider>
<rzslider </article>
rz-slider-floor="0"
rz-slider-ceil="letterMax" <article>
rz-slider-model="letter" <h2>Slider with ticks</h2>
rz-slider-translate="alphabetTranslate" <rzslider
rz-slider-tpl-url="rzSliderTpl.html"></rzslider> rz-slider-model="slider_ticks.value"
</article> rz-slider-options="slider_ticks.options"
></rzslider>
<article> </article>
<h2>Slider with ticks example</h2>
Value: {{ priceSlider4 | json }} <article>
<rzslider rz-slider-model="priceSlider4" <h2>Slider with ticks and values (and tooltips)</h2>
rz-slider-floor="0" <rzslider
rz-slider-ceil="10" rz-slider-model="slider_ticks_values.value"
rz-slider-show-ticks="true"></rzslider> rz-slider-options="slider_ticks_values.options"
</article> ></rzslider>
</article>
<article>
<h2>Slider with ticks value example</h2> <article>
Value: {{ priceSlider5 | json }} <h2>Range slider with ticks and values</h2>
<rzslider rz-slider-model="priceSlider5" <rzslider
rz-slider-floor="0" rz-slider-model="range_slider_ticks_values.minValue"
rz-slider-ceil="10" rz-slider-high="range_slider_ticks_values.maxValue"
rz-slider-show-ticks-value="true"></rzslider> rz-slider-options="range_slider_ticks_values.options"
</article> ></rzslider>
</article>
<article>
<h2>Slider with ticks value and visible bar example</h2> <article>
Value: {{ priceSlider6 | json }} <h2>Slider with draggable range</h2>
<rzslider rz-slider-model="priceSlider6" <rzslider
rz-slider-floor="0.5" rz-slider-model="slider_draggable_range.minValue"
rz-slider-ceil="1.5" rz-slider-high="slider_draggable_range.maxValue"
rz-slider-step="0.1" rz-slider-options="slider_draggable_range.options"
rz-slider-precision="1" ></rzslider>
rz-slider-always-show-bar="true" </article>
rz-slider-show-ticks-value="true"></rzslider>
</article> <article>
<h2>Disabled slider</h2>
<article> <label>Disabled <input type="checkbox" ng-model="disabled_slider.options.disabled"></label>
<h2>Range Slider with ticks value example</h2> <rzslider
Value: {{ priceSlider7 | json }} rz-slider-model="disabled_slider.value"
<rzslider rz-slider-model="priceSlider7.min" rz-slider-options="disabled_slider.options"
rz-slider-high="priceSlider7.max" ></rzslider>
rz-slider-floor="0" </article>
rz-slider-ceil="10"
rz-slider-show-ticks-value="true"></rzslider> <article>
</article> <h2>Read-only slider</h2>
<label>Read-only <input type="checkbox" ng-model="read_only_slider.options.readOnly"></label>
<article> <rzslider
<h2>Draggable range example</h2> rz-slider-model="read_only_slider.value"
Value: rz-slider-options="read_only_slider.options"
<pre>{{ priceSlider | json }}</pre> ></rzslider>
</article>
<input type="text" ng-model="priceSlider.min"/><br/>
<input type="text" ng-model="priceSlider.max"/><br/> <article>
<h2>Toggle slider example</h2>
<rzslider <button ng-click="toggle()">{{ visible ? 'Hide' : 'Show' }}</button>
rz-slider-draggable-range="true" <br/>
rz-slider-floor="priceSlider.floor" <div ng-show="visible">
rz-slider-ceil="priceSlider.ceil" <rzslider
rz-slider-model="priceSlider.min" rz-slider-model="slider_toggle.value"
rz-slider-high="priceSlider.max" rz-slider-options="slider_toggle.options"></rzslider>
rz-slider-step="5" </div>
rz-slider-tpl-url="rzSliderTpl.html"></rzslider> </article>
</article>
<article>
<article> <h2>Sliders inside a modal</h2>
<h2>Toggle slider example</h2> Normal slider value: {{percentages.normal.low}}%
<button ng-click="toggle()">Show</button> </br>
<div ng-show="visible"> Range slider values: {{percentages.range.low}}% and {{percentages.range.high}}%
<rzslider rz-slider-model="toggleSlider.value" </br>
rz-slider-floor="toggleSlider.floor" <button type="button" ng-click="openModal()" class="btn btn-default btn-lg">Open Modal!</button>
rz-slider-ceil="toggleSlider.ceil"></rzslider> </article>
</div>
</article> <article>
<h2>Sliders inside tabs</h2>
<article> <p>Price 1: {{tabSliders.slider1.value}}</p>
<h2>Disabled slider example</h2> <p>Price 2: {{tabSliders.slider2.value}}</p>
<label>Disable slider <input type="checkbox" ng-model="disableSlider"></label> <tabset>
<rzslider rz-slider-model="priceSlider8" <tab heading="Slider 1" select="refreshSlider()">
rz-slider-floor="0" <rzslider rz-slider-model="tabSliders.slider1.value"></rzslider>
rz-slider-ceil="10" </tab>
rz-slider-disabled="disableSlider"></rzslider> <tab heading="Slider 2" select="refreshSlider()">
</article> <rzslider rz-slider-model="tabSliders.slider2.value"></rzslider>
</tab>
</tabset>
</article>
<article>
<h2>Slider with all options demo</h2>
<div class="row all-options">
<div class="col-md-4">
<label class="field-title">Min Value: </label><input type="number" ng-model="slider_all_options.minValue"/><br/>
<label class="field-title"><input type="checkbox" ng-click="toggleHighValue()"> Max Value: </label>
<input type="number" ng-model="slider_all_options.maxValue" ng-disabled="slider_all_options.maxValue == null"/><br/>
<label class="field-title">Floor: </label><input type="number" ng-model="slider_all_options.options.floor"/><br/>
<label class="field-title">Ceil: </label><input type="number" ng-model="slider_all_options.options.ceil"/><br/>
</div>
<div class="col-md-4">
<label class="field-title">Step: </label><input type="number" ng-model="slider_all_options.options.step"/><br/>
<label class="field-title">Precision: </label><input type="number" ng-model="slider_all_options.options.precision"/><br/>
<label>Hide limits <input type="checkbox" ng-model="slider_all_options.options.hideLimitLabels"></label><br/>
<label>Draggable range <input type="checkbox" ng-model="slider_all_options.options.draggableRange"></label>
</div>
<div class="col-md-4">
<label>Show ticks <input type="checkbox" ng-model="slider_all_options.options.showTicks"></label><br/>
<label>Show ticks values <input type="checkbox" ng-model="slider_all_options.options.showTicksValues"></label><br/>
<label>Disabled <input type="checkbox" ng-model="slider_all_options.options.disabled"></label><br/>
<label>Read-Only <input type="checkbox" ng-model="slider_all_options.options.readOnly"></label>
</div>
</div>
<rzslider
rz-slider-model="slider_all_options.minValue"
rz-slider-high="slider_all_options.maxValue"
rz-slider-options="slider_all_options.options"
></rzslider>
</article>
</div> </div>
</body>
<script src="../bower_components/angular/angular.js"></script> <script src="../bower_components/angular/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.js"></script>
<script src="../dist/rzslider.js"></script> <script src="../dist/rzslider.js"></script>
<script> <script src="demo.js"></script>
var app = angular.module('rzSliderDemo', ['rzModule']);
app.controller('MainCtrl', function($scope, $timeout) {
$scope.priceSlider = {
min: 100,
max: 400,
ceil: 500,
floor: 0
};
$scope.priceSlider2 = 150;
$scope.priceSlider3 = 250;
$scope.priceSlider4 = 5;
$scope.priceSlider5 = 5;
$scope.priceSlider6 = 1;
$scope.priceSlider7 = {
min: 2,
max: 8
};
$scope.priceSlider8 = 5;
$scope.disableSlider = true;
$scope.translate = function(value) {
return '$' + value;
};
var alphabetArray = 'abcdefghijklmnopqrstuvwxyz'.split('');
$scope.letter = 5;
$scope.letterMax = alphabetArray.length - 1;
$scope.alphabetTranslate = function(value) {
return alphabetArray[value].toUpperCase();
};
$scope.slider_data = {value: 1};
$scope.otherData = {value: 10};
$scope.onStart = function() {
console.info('started', $scope.slider_data.value);
};
$scope.onChange = function() {
console.info('changed', $scope.slider_data.value);
$scope.otherData.value = $scope.slider_data.value * 10;
};
$scope.onEnd = function() {
console.info('ended', $scope.slider_data.value);
};
$scope.visible = false;
$scope.toggle = function() {
$scope.visible = !$scope.visible;
$timeout(function() {
$scope.$broadcast('rzSliderForceRender');
});
};
$scope.toggleSlider = {
value: 1,
ceil: 500,
floor: 0
};
});
</script>
</body>
</html> </html>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-md-12 col-lg-12">
<label class="control-label">Normal slider into modal</label>
<rzslider
rz-slider-model="percentages.normal.low"
rz-slider-options="percentages.normal.options">
</rzslider>
</div>
</div>
<div class="row">
<div class=" col-md-12 col-lg-12">
<label class="control-label">Range slider into modal</label>
<rzslider
rz-slider-model="percentages.range.low"
rz-slider-high="percentages.range.high"
rz-slider-options="percentages.range.options">
</rzslider>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<button type="button" ng-click="ok()" class="btn btn-primary">Save</button>
<button type="button" ng-click="cancel()" class="btn btn-default">Cancel</button>
</div>
</div>
</div>
</div>
...@@ -16,7 +16,7 @@ rzslider { ...@@ -16,7 +16,7 @@ rzslider {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
height: 4px; height: 4px;
margin: 30px 0 15px 0; margin: 35px 0 15px 0;
vertical-align: middle; vertical-align: middle;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
...@@ -55,6 +55,10 @@ rzslider .rz-bar-wrapper { ...@@ -55,6 +55,10 @@ rzslider .rz-bar-wrapper {
box-sizing: border-box; box-sizing: border-box;
} }
rzslider .rz-bar-wrapper.rz-draggable {
cursor: move;
}
rzslider .rz-bar { rzslider .rz-bar {
left: 0; left: 0;
z-index: 1; z-index: 1;
......
...@@ -4,14 +4,14 @@ ...@@ -4,14 +4,14 @@
* (c) Rafal Zajac <rzajac@gmail.com> * (c) Rafal Zajac <rzajac@gmail.com>
* http://github.com/rzajac/angularjs-slider * http://github.com/rzajac/angularjs-slider
* *
* Version: v1.1.0 * Version: v1.0.0
* *
* Licensed under the MIT license * Licensed under the MIT license
*/ */
/*jslint unparam: true */ /*jslint unparam: true */
/*global angular: false, console: false, define, module */ /*global angular: false, console: false, define, module */
(function (root, factory) { (function(root, factory) {
'use strict'; 'use strict';
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module. // AMD. Register as an anonymous module.
...@@ -27,1458 +27,1366 @@ ...@@ -27,1458 +27,1366 @@
factory(root.angular); factory(root.angular);
} }
}(this, function (angular) { }(this, function(angular) {
'use strict'; 'use strict';
var module = angular.module('rzModule', []) var module = angular.module('rzModule', [])
.value('throttle', .factory('RzSliderOptions', function() {
/** var defaultOptions = {
* throttle floor: 0,
* ceil: null, //defaults to rz-slider-model
* Taken from underscore project step: 1,
* precision: 0,
* @param {Function} func id: null,
* @param {number} wait translate: null,
* @param {ThrottleOptions} options stepsArray: null,
* @returns {Function} draggableRange: false,
*/ showSelectionBar: false,
function throttle(func, wait, options) { hideLimitLabels: false,
'use strict'; readOnly: false,
var getTime = (Date.now || function() { disabled: false,
return new Date().getTime(); interval: 350,
}); showTicks: false,
var context, args, result; showTicksValues: false,
var timeout = null; ticksValuesTooltip: null,
var previous = 0; scale: 1,
options = options || {}; onStart: null,
var later = function() { onChange: null,
previous = options.leading === false ? 0 : getTime(); onEnd: null
timeout = null; };
result = func.apply(context, args); var globalOptions = {};
context = args = null;
};
return function() {
var now = getTime();
if (!previous && options.leading === false) { previous = now; }
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
})
.factory('RzSlider', ['$timeout', '$document', '$window', 'throttle', function($timeout, $document, $window, throttle)
{
'use strict';
/**
* Slider
*
* @param {ngScope} scope The AngularJS scope
* @param {Element} sliderElem The slider directive element wrapped in jqLite
* @param {*} attributes The slider directive attributes
* @constructor
*/
var Slider = function(scope, sliderElem, attributes)
{
/**
* The slider's scope
*
* @type {ngScope}
*/
this.scope = scope;
/**
* The slider attributes
*
* @type {Object}
*/
this.attributes = attributes;
/**
* Slider element wrapped in jqLite
*
* @type {jqLite}
*/
this.sliderElem = sliderElem;
/**
* Slider type
*
* @type {boolean} Set to true for range slider
*/
this.range = attributes.rzSliderHigh !== undefined && attributes.rzSliderModel !== undefined;
/**
* Whether to allow draggable range
*
* @type {boolean} Set to true for draggable range slider
*/
this.dragRange = this.range && attributes.rzSliderDraggableRange === 'true';
var factory = {};
/** /**
* Values recorded when first dragging the bar * `options({})` allows global configuration of all sliders in the
* application.
* *
* @type {Object} * var app = angular.module( 'App', ['rzModule'], function( RzSliderOptions ) {
* // show ticks for all sliders
* RzSliderOptions.options( { showTicks: true } );
* });
*/ */
this.dragging = { factory.options = function(value) {
active: false, angular.extend(globalOptions, value);
value: 0,
difference: 0,
offset: 0,
lowDist: 0,
highDist: 0
}; };
/** factory.getOptions = function(options) {
* Half of the width of the slider handles return angular.extend({}, defaultOptions, globalOptions, options);
* };
* @type {number}
*/
this.handleHalfWidth = 0;
/** return factory;
* Always show selection bar })
*
* @type {boolean}
*/
this.alwaysShowBar = !!attributes.rzSliderAlwaysShowBar;
.value('rzThrottle',
/** /**
* Maximum left the slider handle can have * rzThrottle
* *
* @type {number} * Taken from underscore project
*/
this.maxLeft = 0;
/**
* Precision
* *
* @type {number} * @param {Function} func
* @param {number} wait
* @param {ThrottleOptions} options
* @returns {Function}
*/ */
this.precision = 0; function throttle(func, wait, options) {
'use strict';
var getTime = (Date.now || function() {
return new Date().getTime();
});
var context, args, result;
var timeout = null;
var previous = 0;
options = options || {};
var later = function() {
previous = options.leading === false ? 0 : getTime();
timeout = null;
result = func.apply(context, args);
context = args = null;
};
return function() {
var now = getTime();
if (!previous && options.leading === false) {
previous = now;
}
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
})
/** .factory('RzSlider', ['$timeout', '$document', '$window', '$compile', 'RzSliderOptions', 'rzThrottle', function($timeout, $document, $window, $compile, RzSliderOptions, rzThrottle) {
* Step 'use strict';
*
* @type {number}
*/
this.step = 0;
/** /**
* The name of the handle we are currently tracking * Slider
* *
* @type {string} * @param {ngScope} scope The AngularJS scope
* @param {Element} sliderElem The slider directive element wrapped in jqLite
* @constructor
*/ */
this.tracking = ''; var Slider = function(scope, sliderElem) {
/**
* The slider's scope
*
* @type {ngScope}
*/
this.scope = scope;
/** /**
* Minimum value (floor) of the model * Slider element wrapped in jqLite
* *
* @type {number} * @type {jqLite}
*/ */
this.minValue = 0; this.sliderElem = sliderElem;
/** /**
* Maximum value (ceiling) of the model * Slider type
* *
* @type {number} * @type {boolean} Set to true for range slider
*/ */
this.maxValue = 0; this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
/** /**
* Hide limit labels * Values recorded when first dragging the bar
* *
* @type {boolean} * @type {Object}
*/ */
this.hideLimitLabels = !!attributes.rzSliderHideLimitLabels; this.dragging = {
active: false,
value: 0,
difference: 0,
offset: 0,
lowDist: 0,
highDist: 0
};
/** /**
* Only present model values * Half of the width of the slider handles
* *
* Do not allow to change values * @type {number}
* */
* @type {boolean} this.handleHalfWidth = 0;
*/
this.presentOnly = attributes.rzSliderPresentOnly === 'true';
/** /**
* Display ticks on each possible value. * Maximum left the slider handle can have
* *
* @type {boolean} * @type {number}
*/ */
this.showTicks = attributes.rzSliderShowTicks || attributes.rzSliderShowTicksValue; this.maxLeft = 0;
/** /**
* Display the value on each tick. * Precision
* *
* @type {boolean} * @type {number}
*/ */
this.showTicksValue = attributes.rzSliderShowTicksValue; this.precision = 0;
/** /**
* Disable the slider * Step
* *
* @type {boolean} * @type {number}
*/ */
this.disabled = this.scope.rzSliderDisabled; this.step = 0;
/** /**
* The interval at which the slider updates when the model/high values * The name of the handle we are currently tracking
* are altered from outside the slider *
* * @type {string}
* @type {number} */
*/ this.tracking = '';
this.interval = this.scope.rzSliderInterval !== null ? this.scope.rzSliderInterval : 350;
/** /**
* The delta between min and max value * Minimum value (floor) of the model
* *
* @type {number} * @type {number}
*/ */
this.valueRange = 0; this.minValue = 0;
/** /**
* Set to true if init method already executed * Maximum value (ceiling) of the model
* *
* @type {boolean} * @type {number}
*/ */
this.initHasRun = false; this.maxValue = 0;
/**
* Custom translate function
*
* @type {function}
*/
this.customTrFn = this.scope.rzSliderTranslate() || function(value) { return String(value); };
/** /**
* Array of de-registration functions to call on $destroy * The delta between min and max value
* *
* @type {Array.<Function>} * @type {number}
*/ */
this.deRegFuncs = []; this.valueRange = 0;
// Slider DOM elements wrapped in jqLite
this.fullBar = null; // The whole slider bar
this.selBar = null; // Highlight between two handles
this.minH = null; // Left slider handle
this.maxH = null; // Right slider handle
this.flrLab = null; // Floor label
this.ceilLab = null; // Ceiling label
this.minLab = null; // Label above the low value
this.maxLab = null; // Label above the high value
this.cmbLab = null; // Combined label
this.ticks = null; // The ticks
// Initialize slider
this.init();
};
// Add instance methods
Slider.prototype = {
/** /**
* Initialize slider * Set to true if init method already executed
* *
* @returns {undefined} * @type {boolean}
*/ */
init: function() this.initHasRun = false;
{
var thrLow, thrHigh, unRegFn, // Slider DOM elements wrapped in jqLite
calcDimFn = angular.bind(this, this.calcViewDimensions), this.fullBar = null; // The whole slider bar
self = this; this.selBar = null; // Highlight between two handles
this.minH = null; // Left slider handle
this.initElemHandles(); this.maxH = null; // Right slider handle
this.addAccessibility(); this.flrLab = null; // Floor label
this.setDisabledState(); this.ceilLab = null; // Ceiling label
this.calcViewDimensions(); this.minLab = null; // Label above the low value
this.setMinAndMax(); this.maxLab = null; // Label above the high value
this.cmbLab = null; // Combined label
this.precision = this.scope.rzSliderPrecision === undefined ? 0 : +this.scope.rzSliderPrecision; this.ticks = null; // The ticks
this.step = this.scope.rzSliderStep === undefined ? 1 : +this.scope.rzSliderStep;
// Initialize slider
$timeout(function() this.init();
{ };
self.updateCeilLab();
self.updateFloorLab();
self.initHandles();
self.bindEvents();
});
// Recalculate slider view dimensions // Add instance methods
unRegFn = this.scope.$on('reCalcViewDimensions', calcDimFn); Slider.prototype = {
this.deRegFuncs.push(unRegFn);
// Recalculate stuff if view port dimensions have changed /**
angular.element($window).on('resize', calcDimFn); * 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();
this.initHasRun = true; $timeout(function() {
self.updateCeilLab();
self.updateFloorLab();
self.initHandles();
self.bindEvents();
});
// Watch for changes to the model // Recalculate slider view dimensions
this.scope.$on('reCalcViewDimensions', calcDimFn);
thrLow = throttle(function() // Recalculate stuff if view port dimensions have changed
{ angular.element($window).on('resize', calcDimFn);
self.setMinAndMax();
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));
self.updateSelectionBar();
self.updateTicksScale();
if(self.range) this.initHasRun = true;
{
self.updateCmbLabel();
}
}, self.interval); // Watch for changes to the model
thrHigh = throttle(function()
{
self.setMinAndMax();
self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
self.updateSelectionBar();
self.updateTicksScale();
self.updateCmbLabel();
}, self.interval);
this.scope.$on('rzSliderForceRender', function()
{
self.resetLabelsValue();
thrLow();
if(self.range) { thrHigh(); }
self.resetSlider();
});
// Watchers thrLow = rzThrottle(function() {
self.setMinAndMax();
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));
self.updateSelectionBar();
self.updateTicksScale();
unRegFn = this.scope.$watch('rzSliderModel', function(newValue, oldValue) if (self.range) {
{ self.updateCmbLabel();
if(newValue === oldValue) { return; } }
thrLow();
});
this.deRegFuncs.push(unRegFn);
unRegFn = this.scope.$watch('rzSliderHigh', function(newValue, oldValue) }, self.options.interval);
{
if(newValue === oldValue) { return; }
thrHigh();
});
this.deRegFuncs.push(unRegFn);
this.scope.$watch('rzSliderFloor', function(newValue, oldValue) thrHigh = rzThrottle(function() {
{ self.setMinAndMax();
if(newValue === oldValue) { return; } self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
self.resetSlider(); self.updateSelectionBar();
}); self.updateTicksScale();
this.deRegFuncs.push(unRegFn); self.updateCmbLabel();
}, self.options.interval);
this.scope.$on('rzSliderForceRender', function() {
self.resetLabelsValue();
thrLow();
if (self.range) {
thrHigh();
}
self.resetSlider();
});
unRegFn = this.scope.$watch('rzSliderCeil', function(newValue, oldValue) // Watchers
{ this.scope.$watch('rzSliderModel', function(newValue, oldValue) {
if(newValue === oldValue) { return; } if (newValue === oldValue)
self.resetSlider(); return;
}); thrLow();
this.deRegFuncs.push(unRegFn); });
unRegFn = this.scope.$watch('rzSliderShowTicks', function(newValue, oldValue) this.scope.$watch('rzSliderHigh', function(newValue, oldValue) {
{ if (newValue === oldValue)
if(newValue === oldValue) { return; } return;
self.resetSlider(); if (newValue != null)
}); thrHigh();
this.deRegFuncs.push(unRegFn); if (self.range && newValue == null || !self.range && newValue != null) {
self.applyOptions();
self.resetSlider();
}
});
unRegFn = this.scope.$watch('rzSliderShowTicksValue', function(newValue, oldValue) this.scope.$watch('rzSliderOptions', function(newValue, oldValue) {
{ if (newValue === oldValue)
if(newValue === oldValue) { return; } return;
self.resetSlider(); self.applyOptions();
}); self.resetSlider();
this.deRegFuncs.push(unRegFn); }, true);
unRegFn = this.scope.$watch('rzSliderDisabled', function(newValue, oldValue) this.scope.$on('$destroy', function() {
{
if(newValue === oldValue) { return; }
self.resetSlider();
if(self.disabled)
self.unbindEvents(); self.unbindEvents();
else angular.element($window).off('resize', calcDimFn);
self.bindEvents(); });
}); },
this.deRegFuncs.push(unRegFn);
this.scope.$on('$destroy', function() /**
{ * Read the user options and apply them to the slider model
self.unbindEvents(); */
angular.element($window).off('resize', calcDimFn); applyOptions: function() {
self.deRegFuncs.map(function(unbind) { unbind(); }); this.options = RzSliderOptions.getOptions(this.scope.rzSliderOptions);
});
}, if (this.options.step <= 0)
this.options.step = 1;
this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
this.options.draggableRange = this.range && this.options.draggableRange;
this.options.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);
// Initialize offset cache properties
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)
* Resets slider this.maxH.css('display', 'none');
* else
* @returns {undefined} this.maxH.css('display', null);
*/
resetSlider: function()
{
this.setMinAndMax();
this.updateCeilLab();
this.updateFloorLab();
this.setDisabledState();
this.calcViewDimensions();
},
/** this.alwaysHide(this.flrLab, this.options.showTicksValues || this.options.hideLimitLabels);
* Set the disabled state based on rzSliderDisabled this.alwaysHide(this.ceilLab, this.options.showTicksValues || this.options.hideLimitLabels);
* this.alwaysHide(this.minLab, this.options.showTicksValues);
* @returns {undefined} this.alwaysHide(this.maxLab, this.options.showTicksValues || !this.range);
*/ this.alwaysHide(this.cmbLab, this.options.showTicksValues || !this.range);
setDisabledState: function() this.alwaysHide(this.selBar, !this.range && !this.options.showSelectionBar);
{
this.disabled = this.scope.rzSliderDisabled;
if(this.disabled) {
this.sliderElem.attr('disabled', 'disabled');
}
else {
this.sliderElem.attr('disabled', null);
}
}, if (!this.options.showTicks)
this.ticks.html('');
/** if (this.options.draggableRange)
* Reset label values this.selBar.addClass('rz-draggable');
* else
* @return {undefined} this.selBar.removeClass('rz-draggable');
*/ },
resetLabelsValue: function()
{
this.minLab.rzsv = undefined;
this.maxLab.rzsv = undefined;
},
/** alwaysHide: function(el, hide) {
* Initialize slider handles positions and labels el.rzAlwaysHide = hide;
* if (hide)
* Run only once during initialization and every time view port changes size this.hideEl(el);
* else
* @returns {undefined} this.showEl(el)
*/ },
initHandles: function()
{
this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel));
/* /**
the order here is important since the selection bar should be * Manage the events bindings based on readOnly and disabled options
updated after the high handle but before the combined label *
* @returns {undefined}
*/ */
if(this.range) manageEventsBindings: function() {
this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh)); if (this.options.disabled || this.options.readOnly)
this.updateSelectionBar(); this.unbindEvents();
if(this.range) else if (!this.options.disabled || !this.options.readOnly)
this.updateCmbLabel(); this.bindEvents();
},
/**
* Set the disabled state based on rzSliderDisabled
*
* @returns {undefined}
*/
setDisabledState: function() {
if (this.options.disabled) {
this.sliderElem.attr('disabled', 'disabled');
} else {
this.sliderElem.attr('disabled', null);
}
},
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 /**
* @param {jqLite} label * Translate value to human readable format
* @param {boolean} [useCustomTr] *
* @returns {undefined} * @param {number|string} value
*/ * @param {jqLite} label
translateFn: function(value, label, useCustomTr) * @param {boolean} [useCustomTr]
{ * @returns {undefined}
useCustomTr = useCustomTr === undefined ? true : useCustomTr; */
translateFn: function(value, label, useCustomTr) {
useCustomTr = useCustomTr === undefined ? true : useCustomTr;
var valStr = (useCustomTr ? this.customTrFn(value) : value).toString(), 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);
// Update width only when length of the label have changed
if(getWidth) { this.getWidth(label); }
},
/**
* Set maximum and minimum values for the slider
*
* @returns {undefined}
*/
setMinAndMax: function()
{
if(this.scope.rzSliderFloor)
{
this.minValue = +this.scope.rzSliderFloor;
}
else
{
this.minValue = this.scope.rzSliderFloor = 0;
}
if(this.scope.rzSliderCeil)
{
this.maxValue = +this.scope.rzSliderCeil;
}
else
{
this.maxValue = this.scope.rzSliderCeil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
}
if(this.scope.rzSliderStep)
{
this.step = +this.scope.rzSliderStep;
}
this.valueRange = this.maxValue - this.minValue; label.text(valStr);
},
/** // Update width only when length of the label have changed
* Set the slider children to variables for easy access if (getWidth) {
* this.getWidth(label);
* 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); /**
* Set maximum and minimum values for the slider and ensure the model and high
// Initialize offset cache properties * value match these limits
this.selBar.rzsl = 0; * @returns {undefined}
this.minH.rzsl = 0; */
this.maxH.rzsl = 0; setMinAndMax: function() {
this.flrLab.rzsl = 0;
this.ceilLab.rzsl = 0;
this.minLab.rzsl = 0;
this.maxLab.rzsl = 0;
this.cmbLab.rzsl = 0;
// Hide limit labels
if(this.hideLimitLabels)
{
this.flrLab.rzAlwaysHide = true;
this.ceilLab.rzAlwaysHide = true;
this.hideEl(this.flrLab);
this.hideEl(this.ceilLab);
}
if(this.showTicksValue) { this.step = +this.options.step;
this.flrLab.rzAlwaysHide = true; this.precision = +this.options.precision;
this.ceilLab.rzAlwaysHide = true;
this.minLab.rzAlwaysHide = true;
this.maxLab.rzAlwaysHide = true;
this.cmbLab.rzAlwaysHide = true;
this.hideEl(this.flrLab);
this.hideEl(this.ceilLab);
this.hideEl(this.minLab);
this.hideEl(this.maxLab);
this.hideEl(this.cmbLab);
}
// Remove stuff not needed in single slider this.scope.rzSliderModel = this.roundStep(this.scope.rzSliderModel);
if(this.range === false) if (this.range)
{ this.scope.rzSliderHigh = this.roundStep(this.scope.rzSliderHigh);
this.cmbLab.remove();
this.maxLab.remove();
// Hide max handle this.minValue = this.roundStep(+this.options.floor);
this.maxH.rzAlwaysHide = true;
this.maxH[0].style.zIndex = '-1000';
this.hideEl(this.maxH);
}
// Show selection bar for single slider or not if (this.options.ceil != null)
if(this.range === false && this.alwaysShowBar === false) this.maxValue = this.roundStep(+this.options.ceil);
{ else
this.maxH.remove(); this.maxValue = this.options.ceil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
this.selBar.remove();
}
// If using draggable range, use appropriate cursor for this.selBar. this.valueRange = this.maxValue - this.minValue;
if (this.dragRange) },
{
this.selBar.css('cursor', 'move');
this.selBar.addClass('rz-draggable');
}
},
/**
* Adds accessibility atributes
*
* Run only once during initialization
*
* @returns {undefined}
*/
addAccessibility: function ()
{
this.sliderElem.attr("role", "slider");
},
/** /**
* Calculate dimensions that are dependent on view port size * Adds accessibility atributes
* *
* Run once during initialization and every time view port changes size. * Run only once during initialization
* *
* @returns {undefined} * @returns {undefined}
*/ */
calcViewDimensions: function () addAccessibility: function() {
{ this.sliderElem.attr("role", "slider");
var handleWidth = this.getWidth(this.minH); },
/**
* 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.handleHalfWidth = handleWidth / 2;
this.barWidth = this.getWidth(this.fullBar); this.barWidth = this.getWidth(this.fullBar);
this.maxLeft = this.barWidth - handleWidth; this.maxLeft = this.barWidth - handleWidth;
this.getWidth(this.sliderElem); this.getWidth(this.sliderElem);
this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left; this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;
if(this.initHasRun) if (this.initHasRun) {
{ this.updateFloorLab();
this.updateFloorLab(); this.updateCeilLab();
this.updateCeilLab(); this.initHandles();
this.initHandles(); }
} },
},
/** /**
* Update the ticks position * Update the ticks position
* *
* @returns {undefined} * @returns {undefined}
*/ */
updateTicksScale: function() { updateTicksScale: function() {
if(!this.showTicks) return; if (!this.options.showTicks) return;
if(!this.step) return; //if step is 0, the following loop will be endless. if (!this.step) return; //if step is 0, the following loop will be endless.
var positions = '', var positions = '',
ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1; ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1;
for (var i = 0; i < ticksCount; i++) { for (var i = 0; i < ticksCount; i++) {
var value = this.roundStep(this.minValue + i * this.step); var value = this.roundStep(this.minValue + i * this.step);
var selectedClass = this.isTickSelected(value) ? 'selected': ''; var selectedClass = this.isTickSelected(value) ? 'selected' : '';
positions += '<li class="tick '+ selectedClass +'">'; positions += '<li class="tick ' + selectedClass + '">';
if(this.showTicksValue) if (this.options.showTicksValues) {
positions += '<span class="tick-value">'+ this.getDisplayValue(value) +'</span>'; var tooltip = '';
positions += '</li>'; if (this.options.ticksValuesTooltip) {
} tooltip = 'uib-tooltip="' + this.options.ticksValuesTooltip(value) + '"';
this.ticks.html(positions); }
}, positions += '<span ' + tooltip + ' class="tick-value">' + this.getDisplayValue(value) + '</span>';
}
isTickSelected: function(value) { positions += '</li>';
var tickLeft = this.valueToOffset(value); }
if(!this.range && this.alwaysShowBar && value <= this.scope.rzSliderModel) this.ticks.html(positions);
return true; if (this.options.ticksValuesTooltip)
if(this.range && value >= this.scope.rzSliderModel && value <= this.scope.rzSliderHigh) $compile(this.ticks.contents())(this.scope);
return true; },
return false;
}, isTickSelected: function(value) {
if (!this.range && this.options.showSelectionBar && value <= this.scope.rzSliderModel)
/** return true;
* Update position of the ceiling label if (this.range && value >= this.scope.rzSliderModel && value <= this.scope.rzSliderHigh)
* return true;
* @returns {undefined} return false;
*/ },
updateCeilLab: function()
{ /**
this.translateFn(this.scope.rzSliderCeil, this.ceilLab); * Update position of the ceiling label
this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw); *
this.getWidth(this.ceilLab); * @returns {undefined}
}, */
updateCeilLab: function() {
/** this.translateFn(this.maxValue, this.ceilLab);
* Update position of the floor label this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw);
* this.getWidth(this.ceilLab);
* @returns {undefined} },
*/
updateFloorLab: function() /**
{ * Update position of the floor label
this.translateFn(this.scope.rzSliderFloor, this.flrLab); *
this.getWidth(this.flrLab); * @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();
});
}
},
/** /**
* Call the onStart callback if defined * Call the onChange callback if defined
* *
* @returns {undefined} * @returns {undefined}
*/ */
callOnStart: function() { callOnChange: function() {
if(this.scope.rzSliderOnStart) { if (this.options.onChange) {
var self = this; var self = this;
$timeout(function() { $timeout(function() {
self.scope.rzSliderOnStart(); self.options.onChange();
}); });
} }
}, },
/** /**
* Call the onChange callback if defined * Call the onEnd callback if defined
* *
* @returns {undefined} * @returns {undefined}
*/ */
callOnChange: function() { callOnEnd: function() {
if(this.scope.rzSliderOnChange) { if (this.options.onEnd) {
var self = this; var self = this;
$timeout(function() { $timeout(function() {
self.scope.rzSliderOnChange(); self.options.onEnd();
}); });
} }
}, },
/** /**
* Call the onEnd callback if defined * Update slider handles and label positions
* *
* @returns {undefined} * @param {string} which
*/ * @param {number} newOffset
callOnEnd: function() { */
if(this.scope.rzSliderOnEnd) { updateHandles: function(which, newOffset) {
var self = this; if (which === 'rzSliderModel') {
$timeout(function() { this.updateLowHandle(newOffset);
self.scope.rzSliderOnEnd(); this.updateSelectionBar();
}); this.updateTicksScale();
}
}, if (this.range) {
this.updateCmbLabel();
}
return;
}
/** if (which === 'rzSliderHigh') {
* Update slider handles and label positions this.updateHighHandle(newOffset);
* this.updateSelectionBar();
* @param {string} which this.updateTicksScale();
* @param {number} newOffset
*/
updateHandles: function(which, newOffset)
{
if(which === 'rzSliderModel')
{
this.updateLowHandle(newOffset);
this.updateSelectionBar();
this.updateTicksScale();
if(this.range) if (this.range) {
{ this.updateCmbLabel();
this.updateCmbLabel(); }
return;
} }
return;
}
if(which === 'rzSliderHigh') // Update both
{ this.updateLowHandle(newOffset);
this.updateHighHandle(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
return; * @returns {undefined}
} */
updateLowHandle: function(newOffset) {
// Update both this.setLeft(this.minH, newOffset);
this.updateLowHandle(newOffset); this.translateFn(this.scope.rzSliderModel, this.minLab);
this.updateHighHandle(newOffset); this.setLeft(this.minLab, newOffset - this.minLab.rzsw / 2 + this.handleHalfWidth);
this.updateSelectionBar();
this.updateTicksScale(); this.shFloorCeil();
this.updateCmbLabel(); },
},
/**
/** * Update high slider handle position and label
* Update low slider handle position and label *
* * @param {number} newOffset
* @param {number} newOffset * @returns {undefined}
* @returns {undefined} */
*/ updateHighHandle: function(newOffset) {
updateLowHandle: function(newOffset) this.setLeft(this.maxH, newOffset);
{ this.translateFn(this.scope.rzSliderHigh, this.maxLab);
this.setLeft(this.minH, newOffset); this.setLeft(this.maxLab, newOffset - this.maxLab.rzsw / 2 + this.handleHalfWidth);
this.translateFn(this.scope.rzSliderModel, this.minLab);
this.setLeft(this.minLab, newOffset - this.minLab.rzsw / 2 + this.handleHalfWidth); this.shFloorCeil();
},
this.shFloorCeil();
}, /**
* Show / hide floor / ceiling label
/** *
* Update high slider handle position and label * @returns {undefined}
* */
* @param {number} newOffset shFloorCeil: function() {
* @returns {undefined} var flHidden = false,
*/ clHidden = false;
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.flrLab.rzsl + this.flrLab.rzsw + 5) {
{ flHidden = true;
clHidden = true; this.hideEl(this.flrLab);
this.hideEl(this.ceilLab); } else {
} flHidden = false;
else this.showEl(this.flrLab);
{ }
clHidden = false;
this.showEl(this.ceilLab);
}
if(this.range) if (this.minLab.rzsl + this.minLab.rzsw >= this.ceilLab.rzsl - this.handleHalfWidth - 10) {
{ clHidden = true;
if(this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10)
{
this.hideEl(this.ceilLab); this.hideEl(this.ceilLab);
} } else {
else if( ! clHidden) clHidden = false;
{
this.showEl(this.ceilLab); this.showEl(this.ceilLab);
} }
// Hide or show floor label if (this.range) {
if(this.maxLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + this.handleHalfWidth) if (this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10) {
{ this.hideEl(this.ceilLab);
this.hideEl(this.flrLab); } else if (!clHidden) {
} this.showEl(this.ceilLab);
else if( ! flHidden) }
{
this.showEl(this.flrLab); // 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
* Update combined label position and value *
* * @returns {undefined}
* @returns {undefined} */
*/ updateCmbLabel: function() {
updateCmbLabel: function() var lowTr, highTr;
{
var lowTr, highTr; if (this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl) {
lowTr = this.getDisplayValue(this.scope.rzSliderModel);
if(this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl) highTr = this.getDisplayValue(this.scope.rzSliderHigh);
{
lowTr = this.getDisplayValue(this.scope.rzSliderModel); this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false);
highTr = this.getDisplayValue(this.scope.rzSliderHigh); this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2);
this.hideEl(this.minLab);
this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false); this.hideEl(this.maxLab);
this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2); this.showEl(this.cmbLab);
this.hideEl(this.minLab); } else {
this.hideEl(this.maxLab); this.showEl(this.maxLab);
this.showEl(this.cmbLab); this.showEl(this.minLab);
} this.hideEl(this.cmbLab);
else }
{ },
this.showEl(this.maxLab);
this.showEl(this.minLab);
this.hideEl(this.cmbLab);
}
},
/**
* Return the translated value if a translate function is provided else the original value
* @param value
* @returns {*}
*/
getDisplayValue: function(value) {
return this.customTrFn ? this.customTrFn(value): value;
},
/** /**
* Round value to step and precision * Return the translated value if a translate function is provided else the original value
* * @param value
* @param {number} value * @returns {*}
* @returns {number} */
*/ getDisplayValue: function(value) {
roundStep: function(value) return this.customTrFn(value, this.options.id);
{ },
var step = this.step,
/**
* Round value to step and precision
*
* @param {number} value
* @returns {number}
*/
roundStep: function(value) {
var step = this.step,
remainder = +((value - this.minValue) % step).toFixed(3), remainder = +((value - this.minValue) % step).toFixed(3),
steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder; steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;
steppedValue = steppedValue.toFixed(this.precision); steppedValue = steppedValue.toFixed(this.precision);
return +steppedValue; return +steppedValue;
}, },
/**
* Hide element
*
* @param element
* @returns {jqLite} The jqLite wrapped DOM element
*/
hideEl: function (element)
{
return element.css({opacity: 0});
},
/**
* Show element
*
* @param element The jqLite wrapped DOM element
* @returns {jqLite} The jqLite
*/
showEl: function (element)
{
if(!!element.rzAlwaysHide) { return element; }
return element.css({opacity: 1});
},
/**
* Set element left offset
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} left
* @returns {number}
*/
setLeft: function (elem, left)
{
elem.rzsl = left;
elem.css({left: left + 'px'});
return left;
},
/**
* Get element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @returns {number}
*/
getWidth: function(elem)
{
var val = elem[0].getBoundingClientRect();
elem.rzsw = val.right - val.left;
return elem.rzsw;
},
/**
* Set element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} width
* @returns {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 (val - this.minValue) * this.maxLeft / this.valueRange || 0;
},
/** /**
* Translate offset to model value * Hide element
* *
* @param {number} offset * @param element
* @returns {number} * @returns {jqLite} The jqLite wrapped DOM element
*/ */
offsetToValue: function(offset) hideEl: function(element) {
{ return element.css({
return (offset / this.maxLeft) * this.valueRange + this.minValue; opacity: 0
}, });
},
// Events
/** /**
* Get the X-coordinate of an event * Show element
* *
* @param {Object} event The event * @param element The jqLite wrapped DOM element
* @returns {number} * @returns {jqLite} The jqLite
*/ */
getEventX: function(event) showEl: function(element) {
{ if (!!element.rzAlwaysHide) {
/* http://stackoverflow.com/a/12336075/282882 */ return element;
//noinspection JSLint }
if('clientX' in event)
{
return event.clientX;
}
return event.originalEvent === undefined ? return element.css({
event.touches[0].clientX opacity: 1
: event.originalEvent.touches[0].clientX; });
}, },
/**
* Set element left offset
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} left
* @returns {number}
*/
setLeft: function(elem, left) {
elem.rzsl = left;
elem.css({
left: left + 'px'
});
return left;
},
/**
* Get element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @returns {number}
*/
getWidth: function(elem) {
var val = elem[0].getBoundingClientRect();
elem.rzsw = (val.right - val.left) * 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
* 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;
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 * Get the X-coordinate of an event
* *
* @returns {undefined} * @param {Object} event The event
*/ * @returns {number}
bindEvents: function() */
{ getEventX: function(event) {
if(this.presentOnly || this.disabled) return; /* http://stackoverflow.com/a/12336075/282882 */
var barTracking, barStart, barMove; //noinspection JSLint
if ('clientX' in event) {
if (this.dragRange) return event.clientX;
{ }
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')); return event.originalEvent === undefined ?
if(this.range) { this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); } event.touches[0].clientX : event.originalEvent.touches[0].clientX;
this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null)); },
this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar));
this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking));
this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar));
this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null));
this.ticks.on('mousedown', angular.bind(this, this.onMove, this.ticks));
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
if(this.range) { this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); }
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 * Get the handle closest to an event.
* *
* @returns {undefined} * @param event {Event} The event
*/ * @returns {jqLite} The handle closest to the event.
unbindEvents: function() */
{ getNearestHandle: function(event) {
this.minH.off(); if (!this.range) {
this.maxH.off(); return this.minH;
this.fullBar.off(); }
this.selBar.off(); var offset = (this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth) * this.options.scale;
this.ticks.off(); 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'));
* onStart event handler if (this.range) {
* this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
* @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 this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null));
* @param {Event} event The event this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar));
* @returns {undefined} this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking));
*/ this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar));
onStart: function (pointer, ref, event) this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null));
{ this.ticks.on('mousedown', angular.bind(this, this.onMove, this.ticks));
var ehMove, ehEnd,
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
if (this.range) {
this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
}
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); eventNames = this.getEventNames(event);
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
if(this.tracking !== '') { return; } if (this.tracking !== '') {
return;
}
// We have to do this in case the HTML where the sliders are on // We have to do this in case the HTML where the sliders are on
// have been animated into view. // have been animated into view.
this.calcViewDimensions(); this.calcViewDimensions();
if(pointer) if (pointer) {
{ this.tracking = ref;
this.tracking = ref; } else {
} pointer = this.getNearestHandle(event);
else this.tracking = pointer === this.minH ? 'rzSliderModel' : 'rzSliderHigh';
{ }
pointer = this.getNearestHandle(event);
this.tracking = pointer === this.minH ? 'rzSliderModel' : 'rzSliderHigh';
}
pointer.addClass('rz-active'); pointer.addClass('rz-active');
ehMove = angular.bind(this, this.dragging.active ? this.onDragMove : this.onMove, pointer); ehMove = angular.bind(this, this.dragging.active ? this.onDragMove : this.onMove, pointer);
ehEnd = angular.bind(this, this.onEnd, ehMove); ehEnd = angular.bind(this, this.onEnd, ehMove);
$document.on(eventNames.moveEvent, ehMove); $document.on(eventNames.moveEvent, ehMove);
$document.one(eventNames.endEvent, ehEnd); $document.one(eventNames.endEvent, ehEnd);
this.callOnStart(); this.callOnStart();
}, },
/** /**
* onMove event handler * onMove event handler
* *
* @param {jqLite} pointer * @param {jqLite} pointer
* @param {Event} event The event * @param {Event} event The event
* @returns {undefined} * @returns {undefined}
*/ */
onMove: function (pointer, event) onMove: function(pointer, event) {
{ var eventX = this.getEventX(event),
var eventX = this.getEventX(event),
sliderLO, newOffset, newValue; sliderLO, newOffset, newValue;
sliderLO = this.sliderElem.rzsl; sliderLO = this.sliderElem.rzsl;
newOffset = eventX - sliderLO - this.handleHalfWidth; newOffset = (eventX - sliderLO - this.handleHalfWidth) * this.options.scale;
if(newOffset <= 0) if (newOffset <= 0) {
{ if (pointer.rzsl === 0)
if(pointer.rzsl === 0) return;
return; newValue = this.minValue;
newValue = this.minValue; newOffset = 0;
newOffset = 0; } else if (newOffset >= this.maxLeft) {
} if (pointer.rzsl === this.maxLeft)
else if(newOffset >= this.maxLeft) return;
{ newValue = this.maxValue;
if(pointer.rzsl === this.maxLeft) newOffset = this.maxLeft;
return; } else {
newValue = this.maxValue; newValue = this.offsetToValue(newOffset);
newOffset = this.maxLeft; newValue = this.roundStep(newValue);
} newOffset = this.valueToOffset(newValue);
else { }
newValue = this.offsetToValue(newOffset); this.positionTrackingHandle(newValue, newOffset);
newValue = this.roundStep(newValue); },
newOffset = this.valueToOffset(newValue);
} /**
this.positionTrackingHandle(newValue, newOffset); * onDragStart event handler
}, *
* Handles dragging of the middle bar.
/** *
* onDragStart event handler * @param {Object} pointer The jqLite wrapped DOM element
* * @param {string} ref One of the refLow, refHigh values
* Handles dragging of the middle bar. * @param {Event} event The event
* * @returns {undefined}
* @param {Object} pointer The jqLite wrapped DOM element */
* @param {string} ref One of the refLow, refHigh values onDragStart: function(pointer, ref, event) {
* @param {Event} event The event var offset = this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth;
* @returns {undefined} this.dragging = {
*/ active: true,
onDragStart: function(pointer, ref, event) value: this.offsetToValue(offset),
{ difference: this.scope.rzSliderHigh - this.scope.rzSliderModel,
var offset = this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth; offset: offset,
this.dragging = { lowDist: offset - this.minH.rzsl,
active: true, highDist: this.maxH.rzsl - offset
value: this.offsetToValue(offset), };
difference: this.scope.rzSliderHigh - this.scope.rzSliderModel, this.minH.addClass('rz-active');
offset: offset, this.maxH.addClass('rz-active');
lowDist: offset - this.minH.rzsl,
highDist: this.maxH.rzsl - offset this.onStart(pointer, ref, event);
}; },
this.minH.addClass('rz-active');
this.maxH.addClass('rz-active'); /**
* onDragMove event handler
this.onStart(pointer, ref, event); *
}, * Handles dragging of the middle bar.
*
/** * @param {jqLite} pointer
* onDragMove event handler * @param {Event} event The event
* * @returns {undefined}
* Handles dragging of the middle bar. */
* onDragMove: function(pointer, event) {
* @param {jqLite} pointer var newOffset = this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth,
* @param {Event} event The event
* @returns {undefined}
*/
onDragMove: function(pointer, event)
{
var newOffset = this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth,
newMinOffset, newMaxOffset, newMinOffset, newMaxOffset,
newMinValue, newMaxValue; newMinValue, newMaxValue;
if (newOffset <= this.dragging.lowDist) if (newOffset <= this.dragging.lowDist) {
{ if (pointer.rzsl === this.dragging.lowDist) {
if (pointer.rzsl === this.dragging.lowDist) { return; } return;
newMinValue = this.minValue; }
newMinOffset = 0; newMinValue = this.minValue;
newMaxValue = this.dragging.difference; newMinOffset = 0;
newMaxOffset = this.valueToOffset(newMaxValue); newMaxValue = this.minValue + this.dragging.difference;
} newMaxOffset = this.valueToOffset(newMaxValue);
else if (newOffset >= this.maxLeft - this.dragging.highDist) } else if (newOffset >= this.maxLeft - this.dragging.highDist) {
{ if (pointer.rzsl === this.dragging.highDist) {
if (pointer.rzsl === this.dragging.highDist) { return; } return;
newMaxValue = this.maxValue; }
newMaxOffset = this.maxLeft; newMaxValue = this.maxValue;
newMinValue = this.maxValue - this.dragging.difference; newMaxOffset = this.maxLeft;
newMinOffset = this.valueToOffset(newMinValue); newMinValue = this.maxValue - this.dragging.difference;
} newMinOffset = this.valueToOffset(newMinValue);
else } else {
{ newMinValue = this.offsetToValue(newOffset - this.dragging.lowDist);
newMinValue = this.offsetToValue(newOffset - this.dragging.lowDist); newMinValue = this.roundStep(newMinValue);
newMinValue = this.roundStep(newMinValue); newMinOffset = this.valueToOffset(newMinValue);
newMinOffset = this.valueToOffset(newMinValue); newMaxValue = newMinValue + this.dragging.difference;
newMaxValue = newMinValue + this.dragging.difference; newMaxOffset = this.valueToOffset(newMaxValue);
newMaxOffset = this.valueToOffset(newMaxValue); }
}
this.positionTrackingBar(newMinValue, newMaxValue, newMinOffset, newMaxOffset); this.positionTrackingBar(newMinValue, newMaxValue, newMinOffset, newMaxOffset);
}, },
/** /**
* Set the new value and offset for the entire bar * Set the new value and offset for the entire bar
* *
* @param {number} newMinValue the new minimum value * @param {number} newMinValue the new minimum value
* @param {number} newMaxValue the new maximum value * @param {number} newMaxValue the new maximum value
* @param {number} newMinOffset the new minimum offset * @param {number} newMinOffset the new minimum offset
* @param {number} newMaxOffset the new maximum offset * @param {number} newMaxOffset the new maximum offset
*/ */
positionTrackingBar: function(newMinValue, newMaxValue, newMinOffset, newMaxOffset) positionTrackingBar: function(newMinValue, newMaxValue, newMinOffset, newMaxOffset) {
{ this.scope.rzSliderModel = newMinValue;
this.scope.rzSliderModel = newMinValue; this.scope.rzSliderHigh = newMaxValue;
this.scope.rzSliderHigh = newMaxValue; this.updateHandles('rzSliderModel', newMinOffset);
this.updateHandles('rzSliderModel', newMinOffset); this.updateHandles('rzSliderHigh', newMaxOffset);
this.updateHandles('rzSliderHigh', newMaxOffset); this.scope.$apply();
this.scope.$apply(); this.callOnChange();
this.callOnChange(); },
},
/** /**
* Set the new value and offset to the current tracking handle * Set the new value and offset to the current tracking handle
* *
* @param {number} newValue new model value * @param {number} newValue new model value
* @param {number} newOffset new offset value * @param {number} newOffset new offset value
*/ */
positionTrackingHandle: function(newValue, newOffset) positionTrackingHandle: function(newValue, newOffset) {
{ if (this.range) {
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 is to check if we need to switch the min and max handles*/ this.scope[this.tracking] = this.scope.rzSliderHigh;
if (this.tracking === 'rzSliderModel' && newValue >= this.scope.rzSliderHigh) this.updateHandles(this.tracking, this.maxH.rzsl);
{ this.tracking = 'rzSliderHigh';
this.scope[this.tracking] = this.scope.rzSliderHigh; this.minH.removeClass('rz-active');
this.updateHandles(this.tracking, this.maxH.rzsl); this.maxH.addClass('rz-active');
this.tracking = 'rzSliderHigh'; /* We need to apply here because we are not sure that we will enter the next block */
this.minH.removeClass('rz-active'); this.scope.$apply();
this.maxH.addClass('rz-active'); this.callOnChange();
/* We need to apply here because we are not sure that we will enter the next block */ } else if (this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel) {
this.scope.$apply(); this.scope[this.tracking] = this.scope.rzSliderModel;
this.callOnChange(); 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.callOnChange();
}
} }
else if(this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel)
{ if (this.scope[this.tracking] !== newValue) {
this.scope[this.tracking] = this.scope.rzSliderModel; this.scope[this.tracking] = newValue;
this.updateHandles(this.tracking, this.minH.rzsl); this.updateHandles(this.tracking, newOffset);
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();
} }
} },
if(this.scope[this.tracking] !== newValue) /**
{ * onEnd event handler
this.scope[this.tracking] = newValue; *
this.updateHandles(this.tracking, newOffset); * @param {Event} event The event
this.scope.$apply(); * @param {Function} ehMove The the bound move event handler
this.callOnChange(); * @returns {undefined}
} */
}, onEnd: function(ehMove, event) {
var moveEventName = this.getEventNames(event).moveEvent;
/** this.minH.removeClass('rz-active');
* onEnd event handler this.maxH.removeClass('rz-active');
*
* @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'); $document.off(moveEventName, ehMove);
this.maxH.removeClass('rz-active');
$document.off(moveEventName, ehMove); this.scope.$emit('slideEnded');
this.tracking = '';
this.scope.$emit('slideEnded'); this.dragging.active = false;
this.tracking = ''; this.callOnEnd();
},
this.dragging.active = false; /**
this.callOnEnd(); * Get event names for move and event end
}, *
* @param {Event} event The event
*
* @return {{moveEvent: string, endEvent: string}}
*/
getEventNames: function(event) {
var eventNames = {
moveEvent: '',
endEvent: ''
};
if (event.touches || (event.originalEvent !== undefined && event.originalEvent.touches)) {
eventNames.moveEvent = 'touchmove';
eventNames.endEvent = 'touchend';
} else {
eventNames.moveEvent = 'mousemove';
eventNames.endEvent = 'mouseup';
}
/** return eventNames;
* Get event names for move and event end
*
* @param {Event} event The event
*
* @return {{moveEvent: string, endEvent: string}}
*/
getEventNames: function(event)
{
var eventNames = {moveEvent: '', endEvent: ''};
if(event.touches || (event.originalEvent !== undefined && event.originalEvent.touches))
{
eventNames.moveEvent = 'touchmove';
eventNames.endEvent = 'touchend';
} }
else };
{
eventNames.moveEvent = 'mousemove';
eventNames.endEvent = 'mouseup';
}
return eventNames;
}
};
return Slider;
}])
.directive('rzslider', ['RzSlider', function(RzSlider)
{
'use strict';
return { return Slider;
restrict: 'E', }])
scope: {
rzSliderFloor: '=?', .directive('rzslider', ['RzSlider', function(RzSlider) {
rzSliderCeil: '=?', 'use strict';
rzSliderStep: '@',
rzSliderPrecision: '@', return {
rzSliderModel: '=?', restrict: 'E',
rzSliderHigh: '=?', scope: {
rzSliderDraggable: '@', rzSliderModel: '=?',
rzSliderTranslate: '&', rzSliderHigh: '=?',
rzSliderHideLimitLabels: '=?', rzSliderOptions: '=?',
rzSliderAlwaysShowBar: '=?', rzSliderTplUrl: '@'
rzSliderPresentOnly: '@', },
rzSliderOnStart: '&',
rzSliderOnChange: '&', /**
rzSliderOnEnd: '&', * Return template URL
rzSliderShowTicks: '=?', *
rzSliderShowTicksValue: '=?', * @param {jqLite} elem
rzSliderDisabled: '=?', * @param {Object} attrs
rzSliderInterval: '=?', * @return {string}
}, */
templateUrl: function(elem, attrs) {
//noinspection JSUnresolvedVariable
return attrs.rzSliderTplUrl || 'rzSliderTpl.html';
},
/** link: function(scope, elem) {
* Return template URL return new RzSlider(scope, elem);
* }
* @param {jqLite} elem };
* @param {Object} attrs }]);
* @return {string}
*/
templateUrl: function(elem, attrs) {
//noinspection JSUnresolvedVariable
return attrs.rzSliderTplUrl || 'rzSliderTpl.html';
},
link: function(scope, elem, attr)
{
return new RzSlider(scope, elem, attr);
}
};
}]);
// IDE assist // IDE assist
/** /**
* @name ngScope * @name ngScope
* *
* @property {number} rzSliderModel * @property {number} rzSliderModel
* @property {number} rzSliderHigh * @property {number} rzSliderHigh
* @property {number} rzSliderCeil * @property {Object} rzSliderOptions
*/ */
/** /**
* @name jqLite * @name jqLite
* *
* @property {number|undefined} rzsl rzslider label left offset * @property {number|undefined} rzsl rzslider label left offset
* @property {number|undefined} rzsw rzslider element width * @property {number|undefined} rzsw rzslider element width
* @property {string|undefined} rzsv rzslider label value/text * @property {string|undefined} rzsv rzslider label value/text
* @property {Function} css * @property {Function} css
* @property {Function} text * @property {Function} text
*/ */
/** /**
* @name Event * @name Event
* @property {Array} touches * @property {Array} touches
* @property {Event} originalEvent * @property {Event} originalEvent
*/ */
/** /**
* @name ThrottleOptions * @name ThrottleOptions
* *
* @property {boolean} leading * @property {boolean} leading
* @property {boolean} trailing * @property {boolean} trailing
*/ */
module.run(['$templateCache', function($templateCache) { module.run(['$templateCache', function($templateCache) {
'use strict'; 'use strict';
......
/*! angularjs-slider - v1.1.0 - (c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com>, https://github.com/rzajac/angularjs-slider.git - 2015-11-07 */ /*! angularjs-slider - v2.0.0 - (c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com>, https://github.com/rzajac/angularjs-slider.git - 2015-11-12 */
rzslider{position:relative;display:inline-block;width:100%;height:4px;margin:30px 0 15px 0;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}rzslider[disabled]{cursor:not-allowed}rzslider[disabled] .rz-pointer{cursor:not-allowed;background-color:#d8e0f3}rzslider span{position:absolute;display:inline-block;white-space:nowrap}rzslider .rz-base{width:100%;height:100%;padding:0}rzslider .rz-bar-wrapper{left:0;z-index:1;width:100%;height:32px;padding-top:16px;margin-top:-16px;box-sizing:border-box}rzslider .rz-bar{left:0;z-index:1;width:100%;height:4px;background:#d8e0f3;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}rzslider .rz-bar.rz-selection{z-index:2;background:#0db9f0;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}rzslider .rz-pointer{top:-14px;z-index:3;width:32px;height:32px;cursor:pointer;background-color:#0db9f0;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px}rzslider .rz-pointer:after{position:absolute;top:12px;left:12px;width:8px;height:8px;background:#fff;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;content:''}rzslider .rz-pointer:hover:after{background-color:#fff}rzslider .rz-pointer.rz-active:after{background-color:#451aff}rzslider .rz-bubble{bottom:16px;padding:1px 3px;color:#55637d;cursor:default}rzslider .rz-bubble.rz-selection{top:16px}rzslider .rz-bubble.rz-limit{color:#55637d}rzslider .rz-ticks{position:absolute;top:-3px;left:0;z-index:1;display:flex;width:100%;padding:0 11px;margin:0;list-style:none;box-sizing:border-box;justify-content:space-between}rzslider .rz-ticks .tick{width:10px;height:10px;text-align:center;cursor:pointer;background:#d8e0f3;border-radius:50%}rzslider .rz-ticks .tick.selected{background:#0db9f0}rzslider .rz-ticks .tick .tick-value{position:absolute;top:-30px;transform:translate(-50%,0)} rzslider{position:relative;display:inline-block;width:100%;height:4px;margin:35px 0 15px 0;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}rzslider[disabled]{cursor:not-allowed}rzslider[disabled] .rz-pointer{cursor:not-allowed;background-color:#d8e0f3}rzslider span{position:absolute;display:inline-block;white-space:nowrap}rzslider .rz-base{width:100%;height:100%;padding:0}rzslider .rz-bar-wrapper{left:0;z-index:1;width:100%;height:32px;padding-top:16px;margin-top:-16px;box-sizing:border-box}rzslider .rz-bar-wrapper.rz-draggable{cursor:move}rzslider .rz-bar{left:0;z-index:1;width:100%;height:4px;background:#d8e0f3;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}rzslider .rz-bar.rz-selection{z-index:2;background:#0db9f0;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}rzslider .rz-pointer{top:-14px;z-index:3;width:32px;height:32px;cursor:pointer;background-color:#0db9f0;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px}rzslider .rz-pointer:after{position:absolute;top:12px;left:12px;width:8px;height:8px;background:#fff;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;content:''}rzslider .rz-pointer:hover:after{background-color:#fff}rzslider .rz-pointer.rz-active:after{background-color:#451aff}rzslider .rz-bubble{bottom:16px;padding:1px 3px;color:#55637d;cursor:default}rzslider .rz-bubble.rz-selection{top:16px}rzslider .rz-bubble.rz-limit{color:#55637d}rzslider .rz-ticks{position:absolute;top:-3px;left:0;z-index:1;display:flex;width:100%;padding:0 11px;margin:0;list-style:none;box-sizing:border-box;justify-content:space-between}rzslider .rz-ticks .tick{width:10px;height:10px;text-align:center;cursor:pointer;background:#d8e0f3;border-radius:50%}rzslider .rz-ticks .tick.selected{background:#0db9f0}rzslider .rz-ticks .tick .tick-value{position:absolute;top:-30px;transform:translate(-50%,0)}
\ No newline at end of file \ No newline at end of file
/*! angularjs-slider - v1.1.0 - (c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com>, https://github.com/rzajac/angularjs-slider.git - 2015-11-07 */ /*! angularjs-slider - v2.0.0 - (c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com>, https://github.com/rzajac/angularjs-slider.git - 2015-11-12 */
!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["angular"],b):"object"==typeof module&&module.exports?module.exports=b(require("angular")):b(a.angular)}(this,function(a){"use strict";var b=a.module("rzModule",[]).value("throttle",function(a,b,c){var d,e,f,g=Date.now||function(){return(new Date).getTime()},h=null,i=0;c=c||{};var j=function(){i=c.leading===!1?0:g(),h=null,f=a.apply(d,e),d=e=null};return function(){var k=g();i||c.leading!==!1||(i=k);var l=b-(k-i);return d=this,e=arguments,0>=l?(clearTimeout(h),h=null,i=k,f=a.apply(d,e),d=e=null):h||c.trailing===!1||(h=setTimeout(j,l)),f}}).factory("RzSlider",["$timeout","$document","$window","throttle",function(b,c,d,e){var f=function(a,b,c){this.scope=a,this.attributes=c,this.sliderElem=b,this.range=void 0!==c.rzSliderHigh&&void 0!==c.rzSliderModel,this.dragRange=this.range&&"true"===c.rzSliderDraggableRange,this.dragging={active:!1,value:0,difference:0,offset:0,lowDist:0,highDist:0},this.handleHalfWidth=0,this.alwaysShowBar=!!c.rzSliderAlwaysShowBar,this.maxLeft=0,this.precision=0,this.step=0,this.tracking="",this.minValue=0,this.maxValue=0,this.hideLimitLabels=!!c.rzSliderHideLimitLabels,this.presentOnly="true"===c.rzSliderPresentOnly,this.showTicks=c.rzSliderShowTicks||c.rzSliderShowTicksValue,this.showTicksValue=c.rzSliderShowTicksValue,this.disabled=this.scope.rzSliderDisabled,this.interval=null!==this.scope.rzSliderInterval?this.scope.rzSliderInterval:350,this.valueRange=0,this.initHasRun=!1,this.customTrFn=this.scope.rzSliderTranslate()||function(a){return String(a)},this.deRegFuncs=[],this.fullBar=null,this.selBar=null,this.minH=null,this.maxH=null,this.flrLab=null,this.ceilLab=null,this.minLab=null,this.maxLab=null,this.cmbLab=null,this.ticks=null,this.init()};return f.prototype={init:function(){var c,f,g,h=a.bind(this,this.calcViewDimensions),i=this;this.initElemHandles(),this.addAccessibility(),this.setDisabledState(),this.calcViewDimensions(),this.setMinAndMax(),this.precision=void 0===this.scope.rzSliderPrecision?0:+this.scope.rzSliderPrecision,this.step=void 0===this.scope.rzSliderStep?1:+this.scope.rzSliderStep,b(function(){i.updateCeilLab(),i.updateFloorLab(),i.initHandles(),i.bindEvents()}),g=this.scope.$on("reCalcViewDimensions",h),this.deRegFuncs.push(g),a.element(d).on("resize",h),this.initHasRun=!0,c=e(function(){i.setMinAndMax(),i.updateLowHandle(i.valueToOffset(i.scope.rzSliderModel)),i.updateSelectionBar(),i.updateTicksScale(),i.range&&i.updateCmbLabel()},i.interval),f=e(function(){i.setMinAndMax(),i.updateHighHandle(i.valueToOffset(i.scope.rzSliderHigh)),i.updateSelectionBar(),i.updateTicksScale(),i.updateCmbLabel()},i.interval),this.scope.$on("rzSliderForceRender",function(){i.resetLabelsValue(),c(),i.range&&f(),i.resetSlider()}),g=this.scope.$watch("rzSliderModel",function(a,b){a!==b&&c()}),this.deRegFuncs.push(g),g=this.scope.$watch("rzSliderHigh",function(a,b){a!==b&&f()}),this.deRegFuncs.push(g),this.scope.$watch("rzSliderFloor",function(a,b){a!==b&&i.resetSlider()}),this.deRegFuncs.push(g),g=this.scope.$watch("rzSliderCeil",function(a,b){a!==b&&i.resetSlider()}),this.deRegFuncs.push(g),g=this.scope.$watch("rzSliderShowTicks",function(a,b){a!==b&&i.resetSlider()}),this.deRegFuncs.push(g),g=this.scope.$watch("rzSliderShowTicksValue",function(a,b){a!==b&&i.resetSlider()}),this.deRegFuncs.push(g),g=this.scope.$watch("rzSliderDisabled",function(a,b){a!==b&&(i.resetSlider(),i.disabled?i.unbindEvents():i.bindEvents())}),this.deRegFuncs.push(g),this.scope.$on("$destroy",function(){i.unbindEvents(),a.element(d).off("resize",h),i.deRegFuncs.map(function(a){a()})})},resetSlider:function(){this.setMinAndMax(),this.updateCeilLab(),this.updateFloorLab(),this.setDisabledState(),this.calcViewDimensions()},setDisabledState:function(){this.disabled=this.scope.rzSliderDisabled,this.disabled?this.sliderElem.attr("disabled","disabled"):this.sliderElem.attr("disabled",null)},resetLabelsValue:function(){this.minLab.rzsv=void 0,this.maxLab.rzsv=void 0},initHandles:function(){this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel)),this.range&&this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh)),this.updateSelectionBar(),this.range&&this.updateCmbLabel(),this.updateTicksScale()},translateFn:function(a,b,c){c=void 0===c?!0:c;var d=(c?this.customTrFn(a):a).toString(),e=!1;(void 0===b.rzsv||b.rzsv.length!==d.length||b.rzsv.length>0&&0===b.rzsw)&&(e=!0,b.rzsv=d),b.text(d),e&&this.getWidth(b)},setMinAndMax:function(){this.scope.rzSliderFloor?this.minValue=+this.scope.rzSliderFloor:this.minValue=this.scope.rzSliderFloor=0,this.scope.rzSliderCeil?this.maxValue=+this.scope.rzSliderCeil:this.maxValue=this.scope.rzSliderCeil=this.range?this.scope.rzSliderHigh:this.scope.rzSliderModel,this.scope.rzSliderStep&&(this.step=+this.scope.rzSliderStep),this.valueRange=this.maxValue-this.minValue},initElemHandles:function(){a.forEach(this.sliderElem.children(),function(b,c){var d=a.element(b);switch(c){case 0:this.fullBar=d;break;case 1:this.selBar=d;break;case 2:this.minH=d;break;case 3:this.maxH=d;break;case 4:this.flrLab=d;break;case 5:this.ceilLab=d;break;case 6:this.minLab=d;break;case 7:this.maxLab=d;break;case 8:this.cmbLab=d;break;case 9:this.ticks=d}},this),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,this.hideLimitLabels&&(this.flrLab.rzAlwaysHide=!0,this.ceilLab.rzAlwaysHide=!0,this.hideEl(this.flrLab),this.hideEl(this.ceilLab)),this.showTicksValue&&(this.flrLab.rzAlwaysHide=!0,this.ceilLab.rzAlwaysHide=!0,this.minLab.rzAlwaysHide=!0,this.maxLab.rzAlwaysHide=!0,this.cmbLab.rzAlwaysHide=!0,this.hideEl(this.flrLab),this.hideEl(this.ceilLab),this.hideEl(this.minLab),this.hideEl(this.maxLab),this.hideEl(this.cmbLab)),this.range===!1&&(this.cmbLab.remove(),this.maxLab.remove(),this.maxH.rzAlwaysHide=!0,this.maxH[0].style.zIndex="-1000",this.hideEl(this.maxH)),this.range===!1&&this.alwaysShowBar===!1&&(this.maxH.remove(),this.selBar.remove()),this.dragRange&&(this.selBar.css("cursor","move"),this.selBar.addClass("rz-draggable"))},addAccessibility:function(){this.sliderElem.attr("role","slider")},calcViewDimensions:function(){var a=this.getWidth(this.minH);this.handleHalfWidth=a/2,this.barWidth=this.getWidth(this.fullBar),this.maxLeft=this.barWidth-a,this.getWidth(this.sliderElem),this.sliderElem.rzsl=this.sliderElem[0].getBoundingClientRect().left,this.initHasRun&&(this.updateFloorLab(),this.updateCeilLab(),this.initHandles())},updateTicksScale:function(){if(this.showTicks&&this.step){for(var a="",b=Math.round((this.maxValue-this.minValue)/this.step)+1,c=0;b>c;c++){var d=this.roundStep(this.minValue+c*this.step),e=this.isTickSelected(d)?"selected":"";a+='<li class="tick '+e+'">',this.showTicksValue&&(a+='<span class="tick-value">'+this.getDisplayValue(d)+"</span>"),a+="</li>"}this.ticks.html(a)}},isTickSelected:function(a){this.valueToOffset(a);return!this.range&&this.alwaysShowBar&&a<=this.scope.rzSliderModel?!0:this.range&&a>=this.scope.rzSliderModel&&a<=this.scope.rzSliderHigh?!0:!1},updateCeilLab:function(){this.translateFn(this.scope.rzSliderCeil,this.ceilLab),this.setLeft(this.ceilLab,this.barWidth-this.ceilLab.rzsw),this.getWidth(this.ceilLab)},updateFloorLab:function(){this.translateFn(this.scope.rzSliderFloor,this.flrLab),this.getWidth(this.flrLab)},callOnStart:function(){if(this.scope.rzSliderOnStart){var a=this;b(function(){a.scope.rzSliderOnStart()})}},callOnChange:function(){if(this.scope.rzSliderOnChange){var a=this;b(function(){a.scope.rzSliderOnChange()})}},callOnEnd:function(){if(this.scope.rzSliderOnEnd){var a=this;b(function(){a.scope.rzSliderOnEnd()})}},updateHandles:function(a,b){return"rzSliderModel"===a?(this.updateLowHandle(b),this.updateSelectionBar(),this.updateTicksScale(),void(this.range&&this.updateCmbLabel())):"rzSliderHigh"===a?(this.updateHighHandle(b),this.updateSelectionBar(),this.updateTicksScale(),void(this.range&&this.updateCmbLabel())):(this.updateLowHandle(b),this.updateHighHandle(b),this.updateSelectionBar(),this.updateTicksScale(),void this.updateCmbLabel())},updateLowHandle:function(a){this.setLeft(this.minH,a),this.translateFn(this.scope.rzSliderModel,this.minLab),this.setLeft(this.minLab,a-this.minLab.rzsw/2+this.handleHalfWidth),this.shFloorCeil()},updateHighHandle:function(a){this.setLeft(this.maxH,a),this.translateFn(this.scope.rzSliderHigh,this.maxLab),this.setLeft(this.maxLab,a-this.maxLab.rzsw/2+this.handleHalfWidth),this.shFloorCeil()},shFloorCeil:function(){var a=!1,b=!1;this.minLab.rzsl<=this.flrLab.rzsl+this.flrLab.rzsw+5?(a=!0,this.hideEl(this.flrLab)):(a=!1,this.showEl(this.flrLab)),this.minLab.rzsl+this.minLab.rzsw>=this.ceilLab.rzsl-this.handleHalfWidth-10?(b=!0,this.hideEl(this.ceilLab)):(b=!1,this.showEl(this.ceilLab)),this.range&&(this.maxLab.rzsl+this.maxLab.rzsw>=this.ceilLab.rzsl-10?this.hideEl(this.ceilLab):b||this.showEl(this.ceilLab),this.maxLab.rzsl<=this.flrLab.rzsl+this.flrLab.rzsw+this.handleHalfWidth?this.hideEl(this.flrLab):a||this.showEl(this.flrLab))},updateSelectionBar:function(){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)},updateCmbLabel:function(){var a,b;this.minLab.rzsl+this.minLab.rzsw+10>=this.maxLab.rzsl?(a=this.getDisplayValue(this.scope.rzSliderModel),b=this.getDisplayValue(this.scope.rzSliderHigh),this.translateFn(a+" - "+b,this.cmbLab,!1),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)):(this.showEl(this.maxLab),this.showEl(this.minLab),this.hideEl(this.cmbLab))},getDisplayValue:function(a){return this.customTrFn?this.customTrFn(a):a},roundStep:function(a){var b=this.step,c=+((a-this.minValue)%b).toFixed(3),d=c>b/2?a+b-c:a-c;return d=d.toFixed(this.precision),+d},hideEl:function(a){return a.css({opacity:0})},showEl:function(a){return a.rzAlwaysHide?a:a.css({opacity:1})},setLeft:function(a,b){return a.rzsl=b,a.css({left:b+"px"}),b},getWidth:function(a){var b=a[0].getBoundingClientRect();return a.rzsw=b.right-b.left,a.rzsw},setWidth:function(a,b){return a.rzsw=b,a.css({width:b+"px"}),b},valueToOffset:function(a){return(a-this.minValue)*this.maxLeft/this.valueRange||0},offsetToValue:function(a){return a/this.maxLeft*this.valueRange+this.minValue},getEventX:function(a){return"clientX"in a?a.clientX:void 0===a.originalEvent?a.touches[0].clientX:a.originalEvent.touches[0].clientX},getNearestHandle:function(a){if(!this.range)return this.minH;var b=this.getEventX(a)-this.sliderElem.rzsl-this.handleHalfWidth;return Math.abs(b-this.minH.rzsl)<Math.abs(b-this.maxH.rzsl)?this.minH:this.maxH},bindEvents:function(){if(!this.presentOnly&&!this.disabled){var b,c,d;this.dragRange?(b="rzSliderDrag",c=this.onDragStart,d=this.onDragMove):(b="rzSliderModel",c=this.onStart,d=this.onMove),this.minH.on("mousedown",a.bind(this,this.onStart,this.minH,"rzSliderModel")),this.range&&this.maxH.on("mousedown",a.bind(this,this.onStart,this.maxH,"rzSliderHigh")),this.fullBar.on("mousedown",a.bind(this,this.onStart,null,null)),this.fullBar.on("mousedown",a.bind(this,this.onMove,this.fullBar)),this.selBar.on("mousedown",a.bind(this,c,null,b)),this.selBar.on("mousedown",a.bind(this,d,this.selBar)),this.ticks.on("mousedown",a.bind(this,this.onStart,null,null)),this.ticks.on("mousedown",a.bind(this,this.onMove,this.ticks)),this.minH.on("touchstart",a.bind(this,this.onStart,this.minH,"rzSliderModel")),this.range&&this.maxH.on("touchstart",a.bind(this,this.onStart,this.maxH,"rzSliderHigh")),this.fullBar.on("touchstart",a.bind(this,this.onStart,null,null)),this.fullBar.on("touchstart",a.bind(this,this.onMove,this.fullBar)),this.selBar.on("touchstart",a.bind(this,c,null,b)),this.selBar.on("touchstart",a.bind(this,d,this.selBar)),this.ticks.on("touchstart",a.bind(this,this.onStart,null,null)),this.ticks.on("touchstart",a.bind(this,this.onMove,this.ticks))}},unbindEvents:function(){this.minH.off(),this.maxH.off(),this.fullBar.off(),this.selBar.off(),this.ticks.off()},onStart:function(b,d,e){var f,g,h=this.getEventNames(e);e.stopPropagation(),e.preventDefault(),""===this.tracking&&(this.calcViewDimensions(),b?this.tracking=d:(b=this.getNearestHandle(e),this.tracking=b===this.minH?"rzSliderModel":"rzSliderHigh"),b.addClass("rz-active"),f=a.bind(this,this.dragging.active?this.onDragMove:this.onMove,b),g=a.bind(this,this.onEnd,f),c.on(h.moveEvent,f),c.one(h.endEvent,g),this.callOnStart())},onMove:function(a,b){var c,d,e,f=this.getEventX(b);if(c=this.sliderElem.rzsl,d=f-c-this.handleHalfWidth,0>=d){if(0===a.rzsl)return;e=this.minValue,d=0}else if(d>=this.maxLeft){if(a.rzsl===this.maxLeft)return;e=this.maxValue,d=this.maxLeft}else e=this.offsetToValue(d),e=this.roundStep(e),d=this.valueToOffset(e);this.positionTrackingHandle(e,d)},onDragStart:function(a,b,c){var d=this.getEventX(c)-this.sliderElem.rzsl-this.handleHalfWidth;this.dragging={active:!0,value:this.offsetToValue(d),difference:this.scope.rzSliderHigh-this.scope.rzSliderModel,offset:d,lowDist:d-this.minH.rzsl,highDist:this.maxH.rzsl-d},this.minH.addClass("rz-active"),this.maxH.addClass("rz-active"),this.onStart(a,b,c)},onDragMove:function(a,b){var c,d,e,f,g=this.getEventX(b)-this.sliderElem.rzsl-this.handleHalfWidth;if(g<=this.dragging.lowDist){if(a.rzsl===this.dragging.lowDist)return;e=this.minValue,c=0,f=this.dragging.difference,d=this.valueToOffset(f)}else if(g>=this.maxLeft-this.dragging.highDist){if(a.rzsl===this.dragging.highDist)return;f=this.maxValue,d=this.maxLeft,e=this.maxValue-this.dragging.difference,c=this.valueToOffset(e)}else e=this.offsetToValue(g-this.dragging.lowDist),e=this.roundStep(e),c=this.valueToOffset(e),f=e+this.dragging.difference,d=this.valueToOffset(f);this.positionTrackingBar(e,f,c,d)},positionTrackingBar:function(a,b,c,d){this.scope.rzSliderModel=a,this.scope.rzSliderHigh=b,this.updateHandles("rzSliderModel",c),this.updateHandles("rzSliderHigh",d),this.scope.$apply(),this.callOnChange()},positionTrackingHandle:function(a,b){this.range&&("rzSliderModel"===this.tracking&&a>=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"),this.scope.$apply(),this.callOnChange()):"rzSliderHigh"===this.tracking&&a<=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"),this.scope.$apply(),this.callOnChange())),this.scope[this.tracking]!==a&&(this.scope[this.tracking]=a,this.updateHandles(this.tracking,b),this.scope.$apply(),this.callOnChange())},onEnd:function(a,b){var d=this.getEventNames(b).moveEvent;this.minH.removeClass("rz-active"),this.maxH.removeClass("rz-active"),c.off(d,a),this.scope.$emit("slideEnded"),this.tracking="",this.dragging.active=!1,this.callOnEnd()},getEventNames:function(a){var b={moveEvent:"",endEvent:""};return a.touches||void 0!==a.originalEvent&&a.originalEvent.touches?(b.moveEvent="touchmove",b.endEvent="touchend"):(b.moveEvent="mousemove",b.endEvent="mouseup"),b}},f}]).directive("rzslider",["RzSlider",function(a){return{restrict:"E",scope:{rzSliderFloor:"=?",rzSliderCeil:"=?",rzSliderStep:"@",rzSliderPrecision:"@",rzSliderModel:"=?",rzSliderHigh:"=?",rzSliderDraggable:"@",rzSliderTranslate:"&",rzSliderHideLimitLabels:"=?",rzSliderAlwaysShowBar:"=?",rzSliderPresentOnly:"@",rzSliderOnStart:"&",rzSliderOnChange:"&",rzSliderOnEnd:"&",rzSliderShowTicks:"=?",rzSliderShowTicksValue:"=?",rzSliderDisabled:"=?",rzSliderInterval:"=?"},templateUrl:function(a,b){return b.rzSliderTplUrl||"rzSliderTpl.html"},link:function(b,c,d){return new a(b,c,d)}}}]);return b.run(["$templateCache",function(a){a.put("rzSliderTpl.html",'<span class=rz-bar-wrapper><span class=rz-bar></span></span> <span class=rz-bar-wrapper><span class="rz-bar rz-selection"></span></span> <span class=rz-pointer></span> <span class=rz-pointer></span> <span class="rz-bubble rz-limit"></span> <span class="rz-bubble rz-limit"></span> <span class=rz-bubble></span> <span class=rz-bubble></span> <span class=rz-bubble></span><ul class=rz-ticks></ul>')}]),b}); !function(a,b){"use strict";"function"==typeof define&&define.amd?define(["angular"],b):"object"==typeof module&&module.exports?module.exports=b(require("angular")):b(a.angular)}(this,function(a){"use strict";var b=a.module("rzModule",[]).factory("RzSliderOptions",function(){var b={floor:0,ceil:null,step:1,precision:0,id:null,translate:null,stepsArray:null,draggableRange:!1,showSelectionBar:!1,hideLimitLabels:!1,readOnly:!1,disabled:!1,interval:350,showTicks:!1,showTicksValues:!1,ticksValuesTooltip:null,scale:1,onStart:null,onChange:null,onEnd:null},c={},d={};return d.options=function(b){a.extend(c,b)},d.getOptions=function(d){return a.extend({},b,c,d)},d}).value("rzThrottle",function(a,b,c){var d,e,f,g=Date.now||function(){return(new Date).getTime()},h=null,i=0;c=c||{};var j=function(){i=c.leading===!1?0:g(),h=null,f=a.apply(d,e),d=e=null};return function(){var k=g();i||c.leading!==!1||(i=k);var l=b-(k-i);return d=this,e=arguments,0>=l?(clearTimeout(h),h=null,i=k,f=a.apply(d,e),d=e=null):h||c.trailing===!1||(h=setTimeout(j,l)),f}}).factory("RzSlider",["$timeout","$document","$window","$compile","RzSliderOptions","rzThrottle",function(b,c,d,e,f,g){var h=function(a,b){this.scope=a,this.sliderElem=b,this.range=void 0!==this.scope.rzSliderModel&&void 0!==this.scope.rzSliderHigh,this.dragging={active:!1,value:0,difference:0,offset:0,lowDist:0,highDist:0},this.handleHalfWidth=0,this.maxLeft=0,this.precision=0,this.step=0,this.tracking="",this.minValue=0,this.maxValue=0,this.valueRange=0,this.initHasRun=!1,this.fullBar=null,this.selBar=null,this.minH=null,this.maxH=null,this.flrLab=null,this.ceilLab=null,this.minLab=null,this.maxLab=null,this.cmbLab=null,this.ticks=null,this.init()};return h.prototype={init:function(){var c,e,f=a.bind(this,this.calcViewDimensions),h=this;this.applyOptions(),this.initElemHandles(),this.manageElementsStyle(),this.addAccessibility(),this.manageEventsBindings(),this.setDisabledState(),this.calcViewDimensions(),this.setMinAndMax(),b(function(){h.updateCeilLab(),h.updateFloorLab(),h.initHandles(),h.bindEvents()}),this.scope.$on("reCalcViewDimensions",f),a.element(d).on("resize",f),this.initHasRun=!0,c=g(function(){h.setMinAndMax(),h.updateLowHandle(h.valueToOffset(h.scope.rzSliderModel)),h.updateSelectionBar(),h.updateTicksScale(),h.range&&h.updateCmbLabel()},h.options.interval),e=g(function(){h.setMinAndMax(),h.updateHighHandle(h.valueToOffset(h.scope.rzSliderHigh)),h.updateSelectionBar(),h.updateTicksScale(),h.updateCmbLabel()},h.options.interval),this.scope.$on("rzSliderForceRender",function(){h.resetLabelsValue(),c(),h.range&&e(),h.resetSlider()}),this.scope.$watch("rzSliderModel",function(a,b){a!==b&&c()}),this.scope.$watch("rzSliderHigh",function(a,b){a!==b&&(null!=a&&e(),(h.range&&null==a||!h.range&&null!=a)&&(h.applyOptions(),h.resetSlider()))}),this.scope.$watch("rzSliderOptions",function(a,b){a!==b&&(h.applyOptions(),h.resetSlider())},!0),this.scope.$on("$destroy",function(){h.unbindEvents(),a.element(d).off("resize",f)})},applyOptions:function(){this.options=f.getOptions(this.scope.rzSliderOptions),this.options.step<=0&&(this.options.step=1),this.range=void 0!==this.scope.rzSliderModel&&void 0!==this.scope.rzSliderHigh,this.options.draggableRange=this.range&&this.options.draggableRange,this.options.showTicks=this.options.showTicks||this.options.showTicksValues,this.options.stepsArray?(this.options.floor=0,this.options.ceil=this.options.stepsArray.length-1,this.options.step=1,this.customTrFn=function(a){return this.options.stepsArray[a]}):this.options.translate?this.customTrFn=this.options.translate:this.customTrFn=function(a){return String(a)}},resetSlider:function(){this.manageElementsStyle(),this.setMinAndMax(),this.updateCeilLab(),this.updateFloorLab(),this.unbindEvents(),this.manageEventsBindings(),this.setDisabledState(),this.calcViewDimensions()},initElemHandles:function(){a.forEach(this.sliderElem.children(),function(b,c){var d=a.element(b);switch(c){case 0:this.fullBar=d;break;case 1:this.selBar=d;break;case 2:this.minH=d;break;case 3:this.maxH=d;break;case 4:this.flrLab=d;break;case 5:this.ceilLab=d;break;case 6:this.minLab=d;break;case 7:this.maxLab=d;break;case 8:this.cmbLab=d;break;case 9:this.ticks=d}},this),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},manageElementsStyle:function(){this.range?this.maxH.css("display",null):this.maxH.css("display","none"),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),this.options.showTicks||this.ticks.html(""),this.options.draggableRange?this.selBar.addClass("rz-draggable"):this.selBar.removeClass("rz-draggable")},alwaysHide:function(a,b){a.rzAlwaysHide=b,b?this.hideEl(a):this.showEl(a)},manageEventsBindings:function(){this.options.disabled||this.options.readOnly?this.unbindEvents():this.options.disabled&&this.options.readOnly||this.bindEvents()},setDisabledState:function(){this.options.disabled?this.sliderElem.attr("disabled","disabled"):this.sliderElem.attr("disabled",null)},resetLabelsValue:function(){this.minLab.rzsv=void 0,this.maxLab.rzsv=void 0},initHandles:function(){this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel)),this.range&&this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh)),this.updateSelectionBar(),this.range&&this.updateCmbLabel(),this.updateTicksScale()},translateFn:function(a,b,c){c=void 0===c?!0:c;var d=String(c?this.customTrFn(a,this.options.id):a),e=!1;(void 0===b.rzsv||b.rzsv.length!==d.length||b.rzsv.length>0&&0===b.rzsw)&&(e=!0,b.rzsv=d),b.text(d),e&&this.getWidth(b)},setMinAndMax:function(){this.step=+this.options.step,this.precision=+this.options.precision,this.scope.rzSliderModel=this.roundStep(this.scope.rzSliderModel),this.range&&(this.scope.rzSliderHigh=this.roundStep(this.scope.rzSliderHigh)),this.minValue=this.roundStep(+this.options.floor),null!=this.options.ceil?this.maxValue=this.roundStep(+this.options.ceil):this.maxValue=this.options.ceil=this.range?this.scope.rzSliderHigh:this.scope.rzSliderModel,this.valueRange=this.maxValue-this.minValue},addAccessibility:function(){this.sliderElem.attr("role","slider")},calcViewDimensions:function(){var a=this.getWidth(this.minH);this.handleHalfWidth=a/2,this.barWidth=this.getWidth(this.fullBar),this.maxLeft=this.barWidth-a,this.getWidth(this.sliderElem),this.sliderElem.rzsl=this.sliderElem[0].getBoundingClientRect().left,this.initHasRun&&(this.updateFloorLab(),this.updateCeilLab(),this.initHandles())},updateTicksScale:function(){if(this.options.showTicks&&this.step){for(var a="",b=Math.round((this.maxValue-this.minValue)/this.step)+1,c=0;b>c;c++){var d=this.roundStep(this.minValue+c*this.step),f=this.isTickSelected(d)?"selected":"";if(a+='<li class="tick '+f+'">',this.options.showTicksValues){var g="";this.options.ticksValuesTooltip&&(g='uib-tooltip="'+this.options.ticksValuesTooltip(d)+'"'),a+="<span "+g+' class="tick-value">'+this.getDisplayValue(d)+"</span>"}a+="</li>"}this.ticks.html(a),this.options.ticksValuesTooltip&&e(this.ticks.contents())(this.scope)}},isTickSelected:function(a){return!this.range&&this.options.showSelectionBar&&a<=this.scope.rzSliderModel?!0:this.range&&a>=this.scope.rzSliderModel&&a<=this.scope.rzSliderHigh?!0:!1},updateCeilLab:function(){this.translateFn(this.maxValue,this.ceilLab),this.setLeft(this.ceilLab,this.barWidth-this.ceilLab.rzsw),this.getWidth(this.ceilLab)},updateFloorLab:function(){this.translateFn(this.minValue,this.flrLab),this.getWidth(this.flrLab)},callOnStart:function(){if(this.options.onStart){var a=this;b(function(){a.options.onStart()})}},callOnChange:function(){if(this.options.onChange){var a=this;b(function(){a.options.onChange()})}},callOnEnd:function(){if(this.options.onEnd){var a=this;b(function(){a.options.onEnd()})}},updateHandles:function(a,b){return"rzSliderModel"===a?(this.updateLowHandle(b),this.updateSelectionBar(),this.updateTicksScale(),void(this.range&&this.updateCmbLabel())):"rzSliderHigh"===a?(this.updateHighHandle(b),this.updateSelectionBar(),this.updateTicksScale(),void(this.range&&this.updateCmbLabel())):(this.updateLowHandle(b),this.updateHighHandle(b),this.updateSelectionBar(),this.updateTicksScale(),void this.updateCmbLabel())},updateLowHandle:function(a){this.setLeft(this.minH,a),this.translateFn(this.scope.rzSliderModel,this.minLab),this.setLeft(this.minLab,a-this.minLab.rzsw/2+this.handleHalfWidth),this.shFloorCeil()},updateHighHandle:function(a){this.setLeft(this.maxH,a),this.translateFn(this.scope.rzSliderHigh,this.maxLab),this.setLeft(this.maxLab,a-this.maxLab.rzsw/2+this.handleHalfWidth),this.shFloorCeil()},shFloorCeil:function(){var a=!1,b=!1;this.minLab.rzsl<=this.flrLab.rzsl+this.flrLab.rzsw+5?(a=!0,this.hideEl(this.flrLab)):(a=!1,this.showEl(this.flrLab)),this.minLab.rzsl+this.minLab.rzsw>=this.ceilLab.rzsl-this.handleHalfWidth-10?(b=!0,this.hideEl(this.ceilLab)):(b=!1,this.showEl(this.ceilLab)),this.range&&(this.maxLab.rzsl+this.maxLab.rzsw>=this.ceilLab.rzsl-10?this.hideEl(this.ceilLab):b||this.showEl(this.ceilLab),this.maxLab.rzsl<=this.flrLab.rzsl+this.flrLab.rzsw+this.handleHalfWidth?this.hideEl(this.flrLab):a||this.showEl(this.flrLab))},updateSelectionBar:function(){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)},updateCmbLabel:function(){var a,b;this.minLab.rzsl+this.minLab.rzsw+10>=this.maxLab.rzsl?(a=this.getDisplayValue(this.scope.rzSliderModel),b=this.getDisplayValue(this.scope.rzSliderHigh),this.translateFn(a+" - "+b,this.cmbLab,!1),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)):(this.showEl(this.maxLab),this.showEl(this.minLab),this.hideEl(this.cmbLab))},getDisplayValue:function(a){return this.customTrFn(a,this.options.id)},roundStep:function(a){var b=this.step,c=+((a-this.minValue)%b).toFixed(3),d=c>b/2?a+b-c:a-c;return d=d.toFixed(this.precision),+d},hideEl:function(a){return a.css({opacity:0})},showEl:function(a){return a.rzAlwaysHide?a:a.css({opacity:1})},setLeft:function(a,b){return a.rzsl=b,a.css({left:b+"px"}),b},getWidth:function(a){var b=a[0].getBoundingClientRect();return a.rzsw=(b.right-b.left)*this.options.scale,a.rzsw},setWidth:function(a,b){return a.rzsw=b,a.css({width:b+"px"}),b},valueToOffset:function(a){return(this.sanitizeOffsetValue(a)-this.minValue)*this.maxLeft/this.valueRange||0},sanitizeOffsetValue:function(a){return Math.min(Math.max(a,this.minValue),this.maxValue)},offsetToValue:function(a){return a/this.maxLeft*this.valueRange+this.minValue},getEventX:function(a){return"clientX"in a?a.clientX:void 0===a.originalEvent?a.touches[0].clientX:a.originalEvent.touches[0].clientX},getNearestHandle:function(a){if(!this.range)return this.minH;var b=(this.getEventX(a)-this.sliderElem.rzsl-this.handleHalfWidth)*this.options.scale;return Math.abs(b-this.minH.rzsl)<Math.abs(b-this.maxH.rzsl)?this.minH:this.maxH},bindEvents:function(){if(!this.options.readOnly&&!this.options.disabled){var b,c,d;this.options.draggableRange?(b="rzSliderDrag",c=this.onDragStart,d=this.onDragMove):(b="rzSliderModel",c=this.onStart,d=this.onMove),this.minH.on("mousedown",a.bind(this,this.onStart,this.minH,"rzSliderModel")),this.range&&this.maxH.on("mousedown",a.bind(this,this.onStart,this.maxH,"rzSliderHigh")),this.fullBar.on("mousedown",a.bind(this,this.onStart,null,null)),this.fullBar.on("mousedown",a.bind(this,this.onMove,this.fullBar)),this.selBar.on("mousedown",a.bind(this,c,null,b)),this.selBar.on("mousedown",a.bind(this,d,this.selBar)),this.ticks.on("mousedown",a.bind(this,this.onStart,null,null)),this.ticks.on("mousedown",a.bind(this,this.onMove,this.ticks)),this.minH.on("touchstart",a.bind(this,this.onStart,this.minH,"rzSliderModel")),this.range&&this.maxH.on("touchstart",a.bind(this,this.onStart,this.maxH,"rzSliderHigh")),this.fullBar.on("touchstart",a.bind(this,this.onStart,null,null)),this.fullBar.on("touchstart",a.bind(this,this.onMove,this.fullBar)),this.selBar.on("touchstart",a.bind(this,c,null,b)),this.selBar.on("touchstart",a.bind(this,d,this.selBar)),this.ticks.on("touchstart",a.bind(this,this.onStart,null,null)),this.ticks.on("touchstart",a.bind(this,this.onMove,this.ticks))}},unbindEvents:function(){this.minH.off(),this.maxH.off(),this.fullBar.off(),this.selBar.off(),this.ticks.off()},onStart:function(b,d,e){var f,g,h=this.getEventNames(e);e.stopPropagation(),e.preventDefault(),""===this.tracking&&(this.calcViewDimensions(),b?this.tracking=d:(b=this.getNearestHandle(e),this.tracking=b===this.minH?"rzSliderModel":"rzSliderHigh"),b.addClass("rz-active"),f=a.bind(this,this.dragging.active?this.onDragMove:this.onMove,b),g=a.bind(this,this.onEnd,f),c.on(h.moveEvent,f),c.one(h.endEvent,g),this.callOnStart())},onMove:function(a,b){var c,d,e,f=this.getEventX(b);if(c=this.sliderElem.rzsl,d=(f-c-this.handleHalfWidth)*this.options.scale,0>=d){if(0===a.rzsl)return;e=this.minValue,d=0}else if(d>=this.maxLeft){if(a.rzsl===this.maxLeft)return;e=this.maxValue,d=this.maxLeft}else e=this.offsetToValue(d),e=this.roundStep(e),d=this.valueToOffset(e);this.positionTrackingHandle(e,d)},onDragStart:function(a,b,c){var d=this.getEventX(c)-this.sliderElem.rzsl-this.handleHalfWidth;this.dragging={active:!0,value:this.offsetToValue(d),difference:this.scope.rzSliderHigh-this.scope.rzSliderModel,offset:d,lowDist:d-this.minH.rzsl,highDist:this.maxH.rzsl-d},this.minH.addClass("rz-active"),this.maxH.addClass("rz-active"),this.onStart(a,b,c)},onDragMove:function(a,b){var c,d,e,f,g=this.getEventX(b)-this.sliderElem.rzsl-this.handleHalfWidth;if(g<=this.dragging.lowDist){if(a.rzsl===this.dragging.lowDist)return;e=this.minValue,c=0,f=this.minValue+this.dragging.difference,d=this.valueToOffset(f)}else if(g>=this.maxLeft-this.dragging.highDist){if(a.rzsl===this.dragging.highDist)return;f=this.maxValue,d=this.maxLeft,e=this.maxValue-this.dragging.difference,c=this.valueToOffset(e)}else e=this.offsetToValue(g-this.dragging.lowDist),e=this.roundStep(e),c=this.valueToOffset(e),f=e+this.dragging.difference,d=this.valueToOffset(f);this.positionTrackingBar(e,f,c,d)},positionTrackingBar:function(a,b,c,d){this.scope.rzSliderModel=a,this.scope.rzSliderHigh=b,this.updateHandles("rzSliderModel",c),this.updateHandles("rzSliderHigh",d),this.scope.$apply(),this.callOnChange()},positionTrackingHandle:function(a,b){this.range&&("rzSliderModel"===this.tracking&&a>=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"),this.scope.$apply(),this.callOnChange()):"rzSliderHigh"===this.tracking&&a<=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"),this.scope.$apply(),this.callOnChange())),this.scope[this.tracking]!==a&&(this.scope[this.tracking]=a,this.updateHandles(this.tracking,b),this.scope.$apply(),this.callOnChange())},onEnd:function(a,b){var d=this.getEventNames(b).moveEvent;this.minH.removeClass("rz-active"),this.maxH.removeClass("rz-active"),c.off(d,a),this.scope.$emit("slideEnded"),this.tracking="",this.dragging.active=!1,this.callOnEnd()},getEventNames:function(a){var b={moveEvent:"",endEvent:""};return a.touches||void 0!==a.originalEvent&&a.originalEvent.touches?(b.moveEvent="touchmove",b.endEvent="touchend"):(b.moveEvent="mousemove",b.endEvent="mouseup"),b}},h}]).directive("rzslider",["RzSlider",function(a){return{restrict:"E",scope:{rzSliderModel:"=?",rzSliderHigh:"=?",rzSliderOptions:"=?",rzSliderTplUrl:"@"},templateUrl:function(a,b){return b.rzSliderTplUrl||"rzSliderTpl.html"},link:function(b,c){return new a(b,c)}}}]);return b.run(["$templateCache",function(a){a.put("rzSliderTpl.html",'<span class=rz-bar-wrapper><span class=rz-bar></span></span> <span class=rz-bar-wrapper><span class="rz-bar rz-selection"></span></span> <span class=rz-pointer></span> <span class=rz-pointer></span> <span class="rz-bubble rz-limit"></span> <span class="rz-bubble rz-limit"></span> <span class=rz-bubble></span> <span class=rz-bubble></span> <span class=rz-bubble></span><ul class=rz-ticks></ul>')}]),b});
\ No newline at end of file \ No newline at end of file
{ {
"name": "angularjs-slider", "name": "angularjs-slider",
"version": "1.1.0", "version": "2.0.0",
"description": "AngularJS slider directive with no external dependencies. Mobile friendly!.", "description": "AngularJS slider directive with no external dependencies. Mobile friendly!.",
"main": "dist/rzslider.js", "main": "dist/rzslider.js",
"repository": { "repository": {
......
...@@ -4,14 +4,14 @@ ...@@ -4,14 +4,14 @@
* (c) Rafal Zajac <rzajac@gmail.com> * (c) Rafal Zajac <rzajac@gmail.com>
* http://github.com/rzajac/angularjs-slider * http://github.com/rzajac/angularjs-slider
* *
* Version: v1.1.0 * Version: v1.0.0
* *
* Licensed under the MIT license * Licensed under the MIT license
*/ */
/*jslint unparam: true */ /*jslint unparam: true */
/*global angular: false, console: false, define, module */ /*global angular: false, console: false, define, module */
(function (root, factory) { (function(root, factory) {
'use strict'; 'use strict';
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module. // AMD. Register as an anonymous module.
...@@ -27,1458 +27,1366 @@ ...@@ -27,1458 +27,1366 @@
factory(root.angular); factory(root.angular);
} }
}(this, function (angular) { }(this, function(angular) {
'use strict'; 'use strict';
var module = angular.module('rzModule', []) var module = angular.module('rzModule', [])
.value('throttle', .factory('RzSliderOptions', function() {
/** var defaultOptions = {
* throttle floor: 0,
* ceil: null, //defaults to rz-slider-model
* Taken from underscore project step: 1,
* precision: 0,
* @param {Function} func id: null,
* @param {number} wait translate: null,
* @param {ThrottleOptions} options stepsArray: null,
* @returns {Function} draggableRange: false,
*/ showSelectionBar: false,
function throttle(func, wait, options) { hideLimitLabels: false,
'use strict'; readOnly: false,
var getTime = (Date.now || function() { disabled: false,
return new Date().getTime(); interval: 350,
}); showTicks: false,
var context, args, result; showTicksValues: false,
var timeout = null; ticksValuesTooltip: null,
var previous = 0; scale: 1,
options = options || {}; onStart: null,
var later = function() { onChange: null,
previous = options.leading === false ? 0 : getTime(); onEnd: null
timeout = null; };
result = func.apply(context, args); var globalOptions = {};
context = args = null;
};
return function() {
var now = getTime();
if (!previous && options.leading === false) { previous = now; }
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
})
.factory('RzSlider', function($timeout, $document, $window, throttle)
{
'use strict';
/**
* Slider
*
* @param {ngScope} scope The AngularJS scope
* @param {Element} sliderElem The slider directive element wrapped in jqLite
* @param {*} attributes The slider directive attributes
* @constructor
*/
var Slider = function(scope, sliderElem, attributes)
{
/**
* The slider's scope
*
* @type {ngScope}
*/
this.scope = scope;
/**
* The slider attributes
*
* @type {Object}
*/
this.attributes = attributes;
/**
* Slider element wrapped in jqLite
*
* @type {jqLite}
*/
this.sliderElem = sliderElem;
/**
* Slider type
*
* @type {boolean} Set to true for range slider
*/
this.range = attributes.rzSliderHigh !== undefined && attributes.rzSliderModel !== undefined;
/**
* Whether to allow draggable range
*
* @type {boolean} Set to true for draggable range slider
*/
this.dragRange = this.range && attributes.rzSliderDraggableRange === 'true';
var factory = {};
/** /**
* Values recorded when first dragging the bar * `options({})` allows global configuration of all sliders in the
* application.
* *
* @type {Object} * var app = angular.module( 'App', ['rzModule'], function( RzSliderOptions ) {
* // show ticks for all sliders
* RzSliderOptions.options( { showTicks: true } );
* });
*/ */
this.dragging = { factory.options = function(value) {
active: false, angular.extend(globalOptions, value);
value: 0,
difference: 0,
offset: 0,
lowDist: 0,
highDist: 0
}; };
/** factory.getOptions = function(options) {
* Half of the width of the slider handles return angular.extend({}, defaultOptions, globalOptions, options);
* };
* @type {number}
*/
this.handleHalfWidth = 0;
/** return factory;
* Always show selection bar })
*
* @type {boolean}
*/
this.alwaysShowBar = !!attributes.rzSliderAlwaysShowBar;
.value('rzThrottle',
/** /**
* Maximum left the slider handle can have * rzThrottle
* *
* @type {number} * Taken from underscore project
*/
this.maxLeft = 0;
/**
* Precision
* *
* @type {number} * @param {Function} func
* @param {number} wait
* @param {ThrottleOptions} options
* @returns {Function}
*/ */
this.precision = 0; function throttle(func, wait, options) {
'use strict';
var getTime = (Date.now || function() {
return new Date().getTime();
});
var context, args, result;
var timeout = null;
var previous = 0;
options = options || {};
var later = function() {
previous = options.leading === false ? 0 : getTime();
timeout = null;
result = func.apply(context, args);
context = args = null;
};
return function() {
var now = getTime();
if (!previous && options.leading === false) {
previous = now;
}
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
})
/** .factory('RzSlider', function($timeout, $document, $window, $compile, RzSliderOptions, rzThrottle) {
* Step 'use strict';
*
* @type {number}
*/
this.step = 0;
/** /**
* The name of the handle we are currently tracking * Slider
* *
* @type {string} * @param {ngScope} scope The AngularJS scope
* @param {Element} sliderElem The slider directive element wrapped in jqLite
* @constructor
*/ */
this.tracking = ''; var Slider = function(scope, sliderElem) {
/**
* The slider's scope
*
* @type {ngScope}
*/
this.scope = scope;
/** /**
* Minimum value (floor) of the model * Slider element wrapped in jqLite
* *
* @type {number} * @type {jqLite}
*/ */
this.minValue = 0; this.sliderElem = sliderElem;
/** /**
* Maximum value (ceiling) of the model * Slider type
* *
* @type {number} * @type {boolean} Set to true for range slider
*/ */
this.maxValue = 0; this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
/** /**
* Hide limit labels * Values recorded when first dragging the bar
* *
* @type {boolean} * @type {Object}
*/ */
this.hideLimitLabels = !!attributes.rzSliderHideLimitLabels; this.dragging = {
active: false,
value: 0,
difference: 0,
offset: 0,
lowDist: 0,
highDist: 0
};
/** /**
* Only present model values * Half of the width of the slider handles
* *
* Do not allow to change values * @type {number}
* */
* @type {boolean} this.handleHalfWidth = 0;
*/
this.presentOnly = attributes.rzSliderPresentOnly === 'true';
/** /**
* Display ticks on each possible value. * Maximum left the slider handle can have
* *
* @type {boolean} * @type {number}
*/ */
this.showTicks = attributes.rzSliderShowTicks || attributes.rzSliderShowTicksValue; this.maxLeft = 0;
/** /**
* Display the value on each tick. * Precision
* *
* @type {boolean} * @type {number}
*/ */
this.showTicksValue = attributes.rzSliderShowTicksValue; this.precision = 0;
/** /**
* Disable the slider * Step
* *
* @type {boolean} * @type {number}
*/ */
this.disabled = this.scope.rzSliderDisabled; this.step = 0;
/** /**
* The interval at which the slider updates when the model/high values * The name of the handle we are currently tracking
* are altered from outside the slider *
* * @type {string}
* @type {number} */
*/ this.tracking = '';
this.interval = this.scope.rzSliderInterval !== null ? this.scope.rzSliderInterval : 350;
/** /**
* The delta between min and max value * Minimum value (floor) of the model
* *
* @type {number} * @type {number}
*/ */
this.valueRange = 0; this.minValue = 0;
/** /**
* Set to true if init method already executed * Maximum value (ceiling) of the model
* *
* @type {boolean} * @type {number}
*/ */
this.initHasRun = false; this.maxValue = 0;
/**
* Custom translate function
*
* @type {function}
*/
this.customTrFn = this.scope.rzSliderTranslate() || function(value) { return String(value); };
/** /**
* Array of de-registration functions to call on $destroy * The delta between min and max value
* *
* @type {Array.<Function>} * @type {number}
*/ */
this.deRegFuncs = []; this.valueRange = 0;
// Slider DOM elements wrapped in jqLite
this.fullBar = null; // The whole slider bar
this.selBar = null; // Highlight between two handles
this.minH = null; // Left slider handle
this.maxH = null; // Right slider handle
this.flrLab = null; // Floor label
this.ceilLab = null; // Ceiling label
this.minLab = null; // Label above the low value
this.maxLab = null; // Label above the high value
this.cmbLab = null; // Combined label
this.ticks = null; // The ticks
// Initialize slider
this.init();
};
// Add instance methods
Slider.prototype = {
/** /**
* Initialize slider * Set to true if init method already executed
* *
* @returns {undefined} * @type {boolean}
*/ */
init: function() this.initHasRun = false;
{
var thrLow, thrHigh, unRegFn, // Slider DOM elements wrapped in jqLite
calcDimFn = angular.bind(this, this.calcViewDimensions), this.fullBar = null; // The whole slider bar
self = this; this.selBar = null; // Highlight between two handles
this.minH = null; // Left slider handle
this.initElemHandles(); this.maxH = null; // Right slider handle
this.addAccessibility(); this.flrLab = null; // Floor label
this.setDisabledState(); this.ceilLab = null; // Ceiling label
this.calcViewDimensions(); this.minLab = null; // Label above the low value
this.setMinAndMax(); this.maxLab = null; // Label above the high value
this.cmbLab = null; // Combined label
this.precision = this.scope.rzSliderPrecision === undefined ? 0 : +this.scope.rzSliderPrecision; this.ticks = null; // The ticks
this.step = this.scope.rzSliderStep === undefined ? 1 : +this.scope.rzSliderStep;
// Initialize slider
$timeout(function() this.init();
{ };
self.updateCeilLab();
self.updateFloorLab();
self.initHandles();
self.bindEvents();
});
// Recalculate slider view dimensions // Add instance methods
unRegFn = this.scope.$on('reCalcViewDimensions', calcDimFn); Slider.prototype = {
this.deRegFuncs.push(unRegFn);
// Recalculate stuff if view port dimensions have changed /**
angular.element($window).on('resize', calcDimFn); * 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();
this.initHasRun = true; $timeout(function() {
self.updateCeilLab();
self.updateFloorLab();
self.initHandles();
self.bindEvents();
});
// Watch for changes to the model // Recalculate slider view dimensions
this.scope.$on('reCalcViewDimensions', calcDimFn);
thrLow = throttle(function() // Recalculate stuff if view port dimensions have changed
{ angular.element($window).on('resize', calcDimFn);
self.setMinAndMax();
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));
self.updateSelectionBar();
self.updateTicksScale();
if(self.range) this.initHasRun = true;
{
self.updateCmbLabel();
}
}, self.interval); // Watch for changes to the model
thrHigh = throttle(function()
{
self.setMinAndMax();
self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
self.updateSelectionBar();
self.updateTicksScale();
self.updateCmbLabel();
}, self.interval);
this.scope.$on('rzSliderForceRender', function()
{
self.resetLabelsValue();
thrLow();
if(self.range) { thrHigh(); }
self.resetSlider();
});
// Watchers thrLow = rzThrottle(function() {
self.setMinAndMax();
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));
self.updateSelectionBar();
self.updateTicksScale();
unRegFn = this.scope.$watch('rzSliderModel', function(newValue, oldValue) if (self.range) {
{ self.updateCmbLabel();
if(newValue === oldValue) { return; } }
thrLow();
});
this.deRegFuncs.push(unRegFn);
unRegFn = this.scope.$watch('rzSliderHigh', function(newValue, oldValue) }, self.options.interval);
{
if(newValue === oldValue) { return; }
thrHigh();
});
this.deRegFuncs.push(unRegFn);
this.scope.$watch('rzSliderFloor', function(newValue, oldValue) thrHigh = rzThrottle(function() {
{ self.setMinAndMax();
if(newValue === oldValue) { return; } self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
self.resetSlider(); self.updateSelectionBar();
}); self.updateTicksScale();
this.deRegFuncs.push(unRegFn); self.updateCmbLabel();
}, self.options.interval);
this.scope.$on('rzSliderForceRender', function() {
self.resetLabelsValue();
thrLow();
if (self.range) {
thrHigh();
}
self.resetSlider();
});
unRegFn = this.scope.$watch('rzSliderCeil', function(newValue, oldValue) // Watchers
{ this.scope.$watch('rzSliderModel', function(newValue, oldValue) {
if(newValue === oldValue) { return; } if (newValue === oldValue)
self.resetSlider(); return;
}); thrLow();
this.deRegFuncs.push(unRegFn); });
unRegFn = this.scope.$watch('rzSliderShowTicks', function(newValue, oldValue) this.scope.$watch('rzSliderHigh', function(newValue, oldValue) {
{ if (newValue === oldValue)
if(newValue === oldValue) { return; } return;
self.resetSlider(); if (newValue != null)
}); thrHigh();
this.deRegFuncs.push(unRegFn); if (self.range && newValue == null || !self.range && newValue != null) {
self.applyOptions();
self.resetSlider();
}
});
unRegFn = this.scope.$watch('rzSliderShowTicksValue', function(newValue, oldValue) this.scope.$watch('rzSliderOptions', function(newValue, oldValue) {
{ if (newValue === oldValue)
if(newValue === oldValue) { return; } return;
self.resetSlider(); self.applyOptions();
}); self.resetSlider();
this.deRegFuncs.push(unRegFn); }, true);
unRegFn = this.scope.$watch('rzSliderDisabled', function(newValue, oldValue) this.scope.$on('$destroy', function() {
{
if(newValue === oldValue) { return; }
self.resetSlider();
if(self.disabled)
self.unbindEvents(); self.unbindEvents();
else angular.element($window).off('resize', calcDimFn);
self.bindEvents(); });
}); },
this.deRegFuncs.push(unRegFn);
this.scope.$on('$destroy', function() /**
{ * Read the user options and apply them to the slider model
self.unbindEvents(); */
angular.element($window).off('resize', calcDimFn); applyOptions: function() {
self.deRegFuncs.map(function(unbind) { unbind(); }); this.options = RzSliderOptions.getOptions(this.scope.rzSliderOptions);
});
}, if (this.options.step <= 0)
this.options.step = 1;
this.range = this.scope.rzSliderModel !== undefined && this.scope.rzSliderHigh !== undefined;
this.options.draggableRange = this.range && this.options.draggableRange;
this.options.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);
// Initialize offset cache properties
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)
* Resets slider this.maxH.css('display', 'none');
* else
* @returns {undefined} this.maxH.css('display', null);
*/
resetSlider: function()
{
this.setMinAndMax();
this.updateCeilLab();
this.updateFloorLab();
this.setDisabledState();
this.calcViewDimensions();
},
/** this.alwaysHide(this.flrLab, this.options.showTicksValues || this.options.hideLimitLabels);
* Set the disabled state based on rzSliderDisabled this.alwaysHide(this.ceilLab, this.options.showTicksValues || this.options.hideLimitLabels);
* this.alwaysHide(this.minLab, this.options.showTicksValues);
* @returns {undefined} this.alwaysHide(this.maxLab, this.options.showTicksValues || !this.range);
*/ this.alwaysHide(this.cmbLab, this.options.showTicksValues || !this.range);
setDisabledState: function() this.alwaysHide(this.selBar, !this.range && !this.options.showSelectionBar);
{
this.disabled = this.scope.rzSliderDisabled;
if(this.disabled) {
this.sliderElem.attr('disabled', 'disabled');
}
else {
this.sliderElem.attr('disabled', null);
}
}, if (!this.options.showTicks)
this.ticks.html('');
/** if (this.options.draggableRange)
* Reset label values this.selBar.addClass('rz-draggable');
* else
* @return {undefined} this.selBar.removeClass('rz-draggable');
*/ },
resetLabelsValue: function()
{
this.minLab.rzsv = undefined;
this.maxLab.rzsv = undefined;
},
/** alwaysHide: function(el, hide) {
* Initialize slider handles positions and labels el.rzAlwaysHide = hide;
* if (hide)
* Run only once during initialization and every time view port changes size this.hideEl(el);
* else
* @returns {undefined} this.showEl(el)
*/ },
initHandles: function()
{
this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel));
/* /**
the order here is important since the selection bar should be * Manage the events bindings based on readOnly and disabled options
updated after the high handle but before the combined label *
* @returns {undefined}
*/ */
if(this.range) manageEventsBindings: function() {
this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh)); if (this.options.disabled || this.options.readOnly)
this.updateSelectionBar(); this.unbindEvents();
if(this.range) else if (!this.options.disabled || !this.options.readOnly)
this.updateCmbLabel(); this.bindEvents();
},
/**
* Set the disabled state based on rzSliderDisabled
*
* @returns {undefined}
*/
setDisabledState: function() {
if (this.options.disabled) {
this.sliderElem.attr('disabled', 'disabled');
} else {
this.sliderElem.attr('disabled', null);
}
},
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 /**
* @param {jqLite} label * Translate value to human readable format
* @param {boolean} [useCustomTr] *
* @returns {undefined} * @param {number|string} value
*/ * @param {jqLite} label
translateFn: function(value, label, useCustomTr) * @param {boolean} [useCustomTr]
{ * @returns {undefined}
useCustomTr = useCustomTr === undefined ? true : useCustomTr; */
translateFn: function(value, label, useCustomTr) {
useCustomTr = useCustomTr === undefined ? true : useCustomTr;
var valStr = (useCustomTr ? this.customTrFn(value) : value).toString(), 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);
// Update width only when length of the label have changed
if(getWidth) { this.getWidth(label); }
},
/**
* Set maximum and minimum values for the slider
*
* @returns {undefined}
*/
setMinAndMax: function()
{
if(this.scope.rzSliderFloor)
{
this.minValue = +this.scope.rzSliderFloor;
}
else
{
this.minValue = this.scope.rzSliderFloor = 0;
}
if(this.scope.rzSliderCeil)
{
this.maxValue = +this.scope.rzSliderCeil;
}
else
{
this.maxValue = this.scope.rzSliderCeil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
}
if(this.scope.rzSliderStep)
{
this.step = +this.scope.rzSliderStep;
}
this.valueRange = this.maxValue - this.minValue; label.text(valStr);
},
/** // Update width only when length of the label have changed
* Set the slider children to variables for easy access if (getWidth) {
* this.getWidth(label);
* 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); /**
* Set maximum and minimum values for the slider and ensure the model and high
// Initialize offset cache properties * value match these limits
this.selBar.rzsl = 0; * @returns {undefined}
this.minH.rzsl = 0; */
this.maxH.rzsl = 0; setMinAndMax: function() {
this.flrLab.rzsl = 0;
this.ceilLab.rzsl = 0;
this.minLab.rzsl = 0;
this.maxLab.rzsl = 0;
this.cmbLab.rzsl = 0;
// Hide limit labels
if(this.hideLimitLabels)
{
this.flrLab.rzAlwaysHide = true;
this.ceilLab.rzAlwaysHide = true;
this.hideEl(this.flrLab);
this.hideEl(this.ceilLab);
}
if(this.showTicksValue) { this.step = +this.options.step;
this.flrLab.rzAlwaysHide = true; this.precision = +this.options.precision;
this.ceilLab.rzAlwaysHide = true;
this.minLab.rzAlwaysHide = true;
this.maxLab.rzAlwaysHide = true;
this.cmbLab.rzAlwaysHide = true;
this.hideEl(this.flrLab);
this.hideEl(this.ceilLab);
this.hideEl(this.minLab);
this.hideEl(this.maxLab);
this.hideEl(this.cmbLab);
}
// Remove stuff not needed in single slider this.scope.rzSliderModel = this.roundStep(this.scope.rzSliderModel);
if(this.range === false) if (this.range)
{ this.scope.rzSliderHigh = this.roundStep(this.scope.rzSliderHigh);
this.cmbLab.remove();
this.maxLab.remove();
// Hide max handle this.minValue = this.roundStep(+this.options.floor);
this.maxH.rzAlwaysHide = true;
this.maxH[0].style.zIndex = '-1000';
this.hideEl(this.maxH);
}
// Show selection bar for single slider or not if (this.options.ceil != null)
if(this.range === false && this.alwaysShowBar === false) this.maxValue = this.roundStep(+this.options.ceil);
{ else
this.maxH.remove(); this.maxValue = this.options.ceil = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
this.selBar.remove();
}
// If using draggable range, use appropriate cursor for this.selBar. this.valueRange = this.maxValue - this.minValue;
if (this.dragRange) },
{
this.selBar.css('cursor', 'move');
this.selBar.addClass('rz-draggable');
}
},
/**
* Adds accessibility atributes
*
* Run only once during initialization
*
* @returns {undefined}
*/
addAccessibility: function ()
{
this.sliderElem.attr("role", "slider");
},
/** /**
* Calculate dimensions that are dependent on view port size * Adds accessibility atributes
* *
* Run once during initialization and every time view port changes size. * Run only once during initialization
* *
* @returns {undefined} * @returns {undefined}
*/ */
calcViewDimensions: function () addAccessibility: function() {
{ this.sliderElem.attr("role", "slider");
var handleWidth = this.getWidth(this.minH); },
/**
* 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.handleHalfWidth = handleWidth / 2;
this.barWidth = this.getWidth(this.fullBar); this.barWidth = this.getWidth(this.fullBar);
this.maxLeft = this.barWidth - handleWidth; this.maxLeft = this.barWidth - handleWidth;
this.getWidth(this.sliderElem); this.getWidth(this.sliderElem);
this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left; this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;
if(this.initHasRun) if (this.initHasRun) {
{ this.updateFloorLab();
this.updateFloorLab(); this.updateCeilLab();
this.updateCeilLab(); this.initHandles();
this.initHandles(); }
} },
},
/** /**
* Update the ticks position * Update the ticks position
* *
* @returns {undefined} * @returns {undefined}
*/ */
updateTicksScale: function() { updateTicksScale: function() {
if(!this.showTicks) return; if (!this.options.showTicks) return;
if(!this.step) return; //if step is 0, the following loop will be endless. if (!this.step) return; //if step is 0, the following loop will be endless.
var positions = '', var positions = '',
ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1; ticksCount = Math.round((this.maxValue - this.minValue) / this.step) + 1;
for (var i = 0; i < ticksCount; i++) { for (var i = 0; i < ticksCount; i++) {
var value = this.roundStep(this.minValue + i * this.step); var value = this.roundStep(this.minValue + i * this.step);
var selectedClass = this.isTickSelected(value) ? 'selected': ''; var selectedClass = this.isTickSelected(value) ? 'selected' : '';
positions += '<li class="tick '+ selectedClass +'">'; positions += '<li class="tick ' + selectedClass + '">';
if(this.showTicksValue) if (this.options.showTicksValues) {
positions += '<span class="tick-value">'+ this.getDisplayValue(value) +'</span>'; var tooltip = '';
positions += '</li>'; if (this.options.ticksValuesTooltip) {
} tooltip = 'uib-tooltip="' + this.options.ticksValuesTooltip(value) + '"';
this.ticks.html(positions); }
}, positions += '<span ' + tooltip + ' class="tick-value">' + this.getDisplayValue(value) + '</span>';
}
isTickSelected: function(value) { positions += '</li>';
var tickLeft = this.valueToOffset(value); }
if(!this.range && this.alwaysShowBar && value <= this.scope.rzSliderModel) this.ticks.html(positions);
return true; if (this.options.ticksValuesTooltip)
if(this.range && value >= this.scope.rzSliderModel && value <= this.scope.rzSliderHigh) $compile(this.ticks.contents())(this.scope);
return true; },
return false;
}, isTickSelected: function(value) {
if (!this.range && this.options.showSelectionBar && value <= this.scope.rzSliderModel)
/** return true;
* Update position of the ceiling label if (this.range && value >= this.scope.rzSliderModel && value <= this.scope.rzSliderHigh)
* return true;
* @returns {undefined} return false;
*/ },
updateCeilLab: function()
{ /**
this.translateFn(this.scope.rzSliderCeil, this.ceilLab); * Update position of the ceiling label
this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw); *
this.getWidth(this.ceilLab); * @returns {undefined}
}, */
updateCeilLab: function() {
/** this.translateFn(this.maxValue, this.ceilLab);
* Update position of the floor label this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw);
* this.getWidth(this.ceilLab);
* @returns {undefined} },
*/
updateFloorLab: function() /**
{ * Update position of the floor label
this.translateFn(this.scope.rzSliderFloor, this.flrLab); *
this.getWidth(this.flrLab); * @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();
});
}
},
/** /**
* Call the onStart callback if defined * Call the onChange callback if defined
* *
* @returns {undefined} * @returns {undefined}
*/ */
callOnStart: function() { callOnChange: function() {
if(this.scope.rzSliderOnStart) { if (this.options.onChange) {
var self = this; var self = this;
$timeout(function() { $timeout(function() {
self.scope.rzSliderOnStart(); self.options.onChange();
}); });
} }
}, },
/** /**
* Call the onChange callback if defined * Call the onEnd callback if defined
* *
* @returns {undefined} * @returns {undefined}
*/ */
callOnChange: function() { callOnEnd: function() {
if(this.scope.rzSliderOnChange) { if (this.options.onEnd) {
var self = this; var self = this;
$timeout(function() { $timeout(function() {
self.scope.rzSliderOnChange(); self.options.onEnd();
}); });
} }
}, },
/** /**
* Call the onEnd callback if defined * Update slider handles and label positions
* *
* @returns {undefined} * @param {string} which
*/ * @param {number} newOffset
callOnEnd: function() { */
if(this.scope.rzSliderOnEnd) { updateHandles: function(which, newOffset) {
var self = this; if (which === 'rzSliderModel') {
$timeout(function() { this.updateLowHandle(newOffset);
self.scope.rzSliderOnEnd(); this.updateSelectionBar();
}); this.updateTicksScale();
}
}, if (this.range) {
this.updateCmbLabel();
}
return;
}
/** if (which === 'rzSliderHigh') {
* Update slider handles and label positions this.updateHighHandle(newOffset);
* this.updateSelectionBar();
* @param {string} which this.updateTicksScale();
* @param {number} newOffset
*/
updateHandles: function(which, newOffset)
{
if(which === 'rzSliderModel')
{
this.updateLowHandle(newOffset);
this.updateSelectionBar();
this.updateTicksScale();
if(this.range) if (this.range) {
{ this.updateCmbLabel();
this.updateCmbLabel(); }
return;
} }
return;
}
if(which === 'rzSliderHigh') // Update both
{ this.updateLowHandle(newOffset);
this.updateHighHandle(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
return; * @returns {undefined}
} */
updateLowHandle: function(newOffset) {
// Update both this.setLeft(this.minH, newOffset);
this.updateLowHandle(newOffset); this.translateFn(this.scope.rzSliderModel, this.minLab);
this.updateHighHandle(newOffset); this.setLeft(this.minLab, newOffset - this.minLab.rzsw / 2 + this.handleHalfWidth);
this.updateSelectionBar();
this.updateTicksScale(); this.shFloorCeil();
this.updateCmbLabel(); },
},
/**
/** * Update high slider handle position and label
* Update low slider handle position and label *
* * @param {number} newOffset
* @param {number} newOffset * @returns {undefined}
* @returns {undefined} */
*/ updateHighHandle: function(newOffset) {
updateLowHandle: function(newOffset) this.setLeft(this.maxH, newOffset);
{ this.translateFn(this.scope.rzSliderHigh, this.maxLab);
this.setLeft(this.minH, newOffset); this.setLeft(this.maxLab, newOffset - this.maxLab.rzsw / 2 + this.handleHalfWidth);
this.translateFn(this.scope.rzSliderModel, this.minLab);
this.setLeft(this.minLab, newOffset - this.minLab.rzsw / 2 + this.handleHalfWidth); this.shFloorCeil();
},
this.shFloorCeil();
}, /**
* Show / hide floor / ceiling label
/** *
* Update high slider handle position and label * @returns {undefined}
* */
* @param {number} newOffset shFloorCeil: function() {
* @returns {undefined} var flHidden = false,
*/ clHidden = false;
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.flrLab.rzsl + this.flrLab.rzsw + 5) {
{ flHidden = true;
clHidden = true; this.hideEl(this.flrLab);
this.hideEl(this.ceilLab); } else {
} flHidden = false;
else this.showEl(this.flrLab);
{ }
clHidden = false;
this.showEl(this.ceilLab);
}
if(this.range) if (this.minLab.rzsl + this.minLab.rzsw >= this.ceilLab.rzsl - this.handleHalfWidth - 10) {
{ clHidden = true;
if(this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10)
{
this.hideEl(this.ceilLab); this.hideEl(this.ceilLab);
} } else {
else if( ! clHidden) clHidden = false;
{
this.showEl(this.ceilLab); this.showEl(this.ceilLab);
} }
// Hide or show floor label if (this.range) {
if(this.maxLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + this.handleHalfWidth) if (this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10) {
{ this.hideEl(this.ceilLab);
this.hideEl(this.flrLab); } else if (!clHidden) {
} this.showEl(this.ceilLab);
else if( ! flHidden) }
{
this.showEl(this.flrLab); // 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
* Update combined label position and value *
* * @returns {undefined}
* @returns {undefined} */
*/ updateCmbLabel: function() {
updateCmbLabel: function() var lowTr, highTr;
{
var lowTr, highTr; if (this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl) {
lowTr = this.getDisplayValue(this.scope.rzSliderModel);
if(this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl) highTr = this.getDisplayValue(this.scope.rzSliderHigh);
{
lowTr = this.getDisplayValue(this.scope.rzSliderModel); this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false);
highTr = this.getDisplayValue(this.scope.rzSliderHigh); this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2);
this.hideEl(this.minLab);
this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false); this.hideEl(this.maxLab);
this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2); this.showEl(this.cmbLab);
this.hideEl(this.minLab); } else {
this.hideEl(this.maxLab); this.showEl(this.maxLab);
this.showEl(this.cmbLab); this.showEl(this.minLab);
} this.hideEl(this.cmbLab);
else }
{ },
this.showEl(this.maxLab);
this.showEl(this.minLab);
this.hideEl(this.cmbLab);
}
},
/**
* Return the translated value if a translate function is provided else the original value
* @param value
* @returns {*}
*/
getDisplayValue: function(value) {
return this.customTrFn ? this.customTrFn(value): value;
},
/** /**
* Round value to step and precision * Return the translated value if a translate function is provided else the original value
* * @param value
* @param {number} value * @returns {*}
* @returns {number} */
*/ getDisplayValue: function(value) {
roundStep: function(value) return this.customTrFn(value, this.options.id);
{ },
var step = this.step,
/**
* Round value to step and precision
*
* @param {number} value
* @returns {number}
*/
roundStep: function(value) {
var step = this.step,
remainder = +((value - this.minValue) % step).toFixed(3), remainder = +((value - this.minValue) % step).toFixed(3),
steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder; steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;
steppedValue = steppedValue.toFixed(this.precision); steppedValue = steppedValue.toFixed(this.precision);
return +steppedValue; return +steppedValue;
}, },
/**
* Hide element
*
* @param element
* @returns {jqLite} The jqLite wrapped DOM element
*/
hideEl: function (element)
{
return element.css({opacity: 0});
},
/**
* Show element
*
* @param element The jqLite wrapped DOM element
* @returns {jqLite} The jqLite
*/
showEl: function (element)
{
if(!!element.rzAlwaysHide) { return element; }
return element.css({opacity: 1});
},
/**
* Set element left offset
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} left
* @returns {number}
*/
setLeft: function (elem, left)
{
elem.rzsl = left;
elem.css({left: left + 'px'});
return left;
},
/**
* Get element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @returns {number}
*/
getWidth: function(elem)
{
var val = elem[0].getBoundingClientRect();
elem.rzsw = val.right - val.left;
return elem.rzsw;
},
/**
* Set element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} width
* @returns {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 (val - this.minValue) * this.maxLeft / this.valueRange || 0;
},
/** /**
* Translate offset to model value * Hide element
* *
* @param {number} offset * @param element
* @returns {number} * @returns {jqLite} The jqLite wrapped DOM element
*/ */
offsetToValue: function(offset) hideEl: function(element) {
{ return element.css({
return (offset / this.maxLeft) * this.valueRange + this.minValue; opacity: 0
}, });
},
// Events
/** /**
* Get the X-coordinate of an event * Show element
* *
* @param {Object} event The event * @param element The jqLite wrapped DOM element
* @returns {number} * @returns {jqLite} The jqLite
*/ */
getEventX: function(event) showEl: function(element) {
{ if (!!element.rzAlwaysHide) {
/* http://stackoverflow.com/a/12336075/282882 */ return element;
//noinspection JSLint }
if('clientX' in event)
{
return event.clientX;
}
return event.originalEvent === undefined ? return element.css({
event.touches[0].clientX opacity: 1
: event.originalEvent.touches[0].clientX; });
}, },
/**
* Set element left offset
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} left
* @returns {number}
*/
setLeft: function(elem, left) {
elem.rzsl = left;
elem.css({
left: left + 'px'
});
return left;
},
/**
* Get element width
*
* @param {jqLite} elem The jqLite wrapped DOM element
* @returns {number}
*/
getWidth: function(elem) {
var val = elem[0].getBoundingClientRect();
elem.rzsw = (val.right - val.left) * 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
* 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;
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 * Get the X-coordinate of an event
* *
* @returns {undefined} * @param {Object} event The event
*/ * @returns {number}
bindEvents: function() */
{ getEventX: function(event) {
if(this.presentOnly || this.disabled) return; /* http://stackoverflow.com/a/12336075/282882 */
var barTracking, barStart, barMove; //noinspection JSLint
if ('clientX' in event) {
if (this.dragRange) return event.clientX;
{ }
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')); return event.originalEvent === undefined ?
if(this.range) { this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); } event.touches[0].clientX : event.originalEvent.touches[0].clientX;
this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null)); },
this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar));
this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking));
this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar));
this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null));
this.ticks.on('mousedown', angular.bind(this, this.onMove, this.ticks));
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
if(this.range) { this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')); }
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 * Get the handle closest to an event.
* *
* @returns {undefined} * @param event {Event} The event
*/ * @returns {jqLite} The handle closest to the event.
unbindEvents: function() */
{ getNearestHandle: function(event) {
this.minH.off(); if (!this.range) {
this.maxH.off(); return this.minH;
this.fullBar.off(); }
this.selBar.off(); var offset = (this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth) * this.options.scale;
this.ticks.off(); 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'));
* onStart event handler if (this.range) {
* this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
* @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 this.fullBar.on('mousedown', angular.bind(this, this.onStart, null, null));
* @param {Event} event The event this.fullBar.on('mousedown', angular.bind(this, this.onMove, this.fullBar));
* @returns {undefined} this.selBar.on('mousedown', angular.bind(this, barStart, null, barTracking));
*/ this.selBar.on('mousedown', angular.bind(this, barMove, this.selBar));
onStart: function (pointer, ref, event) this.ticks.on('mousedown', angular.bind(this, this.onStart, null, null));
{ this.ticks.on('mousedown', angular.bind(this, this.onMove, this.ticks));
var ehMove, ehEnd,
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
if (this.range) {
this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh'));
}
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); eventNames = this.getEventNames(event);
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
if(this.tracking !== '') { return; } if (this.tracking !== '') {
return;
}
// We have to do this in case the HTML where the sliders are on // We have to do this in case the HTML where the sliders are on
// have been animated into view. // have been animated into view.
this.calcViewDimensions(); this.calcViewDimensions();
if(pointer) if (pointer) {
{ this.tracking = ref;
this.tracking = ref; } else {
} pointer = this.getNearestHandle(event);
else this.tracking = pointer === this.minH ? 'rzSliderModel' : 'rzSliderHigh';
{ }
pointer = this.getNearestHandle(event);
this.tracking = pointer === this.minH ? 'rzSliderModel' : 'rzSliderHigh';
}
pointer.addClass('rz-active'); pointer.addClass('rz-active');
ehMove = angular.bind(this, this.dragging.active ? this.onDragMove : this.onMove, pointer); ehMove = angular.bind(this, this.dragging.active ? this.onDragMove : this.onMove, pointer);
ehEnd = angular.bind(this, this.onEnd, ehMove); ehEnd = angular.bind(this, this.onEnd, ehMove);
$document.on(eventNames.moveEvent, ehMove); $document.on(eventNames.moveEvent, ehMove);
$document.one(eventNames.endEvent, ehEnd); $document.one(eventNames.endEvent, ehEnd);
this.callOnStart(); this.callOnStart();
}, },
/** /**
* onMove event handler * onMove event handler
* *
* @param {jqLite} pointer * @param {jqLite} pointer
* @param {Event} event The event * @param {Event} event The event
* @returns {undefined} * @returns {undefined}
*/ */
onMove: function (pointer, event) onMove: function(pointer, event) {
{ var eventX = this.getEventX(event),
var eventX = this.getEventX(event),
sliderLO, newOffset, newValue; sliderLO, newOffset, newValue;
sliderLO = this.sliderElem.rzsl; sliderLO = this.sliderElem.rzsl;
newOffset = eventX - sliderLO - this.handleHalfWidth; newOffset = (eventX - sliderLO - this.handleHalfWidth) * this.options.scale;
if(newOffset <= 0) if (newOffset <= 0) {
{ if (pointer.rzsl === 0)
if(pointer.rzsl === 0) return;
return; newValue = this.minValue;
newValue = this.minValue; newOffset = 0;
newOffset = 0; } else if (newOffset >= this.maxLeft) {
} if (pointer.rzsl === this.maxLeft)
else if(newOffset >= this.maxLeft) return;
{ newValue = this.maxValue;
if(pointer.rzsl === this.maxLeft) newOffset = this.maxLeft;
return; } else {
newValue = this.maxValue; newValue = this.offsetToValue(newOffset);
newOffset = this.maxLeft; newValue = this.roundStep(newValue);
} newOffset = this.valueToOffset(newValue);
else { }
newValue = this.offsetToValue(newOffset); this.positionTrackingHandle(newValue, newOffset);
newValue = this.roundStep(newValue); },
newOffset = this.valueToOffset(newValue);
} /**
this.positionTrackingHandle(newValue, newOffset); * onDragStart event handler
}, *
* Handles dragging of the middle bar.
/** *
* onDragStart event handler * @param {Object} pointer The jqLite wrapped DOM element
* * @param {string} ref One of the refLow, refHigh values
* Handles dragging of the middle bar. * @param {Event} event The event
* * @returns {undefined}
* @param {Object} pointer The jqLite wrapped DOM element */
* @param {string} ref One of the refLow, refHigh values onDragStart: function(pointer, ref, event) {
* @param {Event} event The event var offset = this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth;
* @returns {undefined} this.dragging = {
*/ active: true,
onDragStart: function(pointer, ref, event) value: this.offsetToValue(offset),
{ difference: this.scope.rzSliderHigh - this.scope.rzSliderModel,
var offset = this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth; offset: offset,
this.dragging = { lowDist: offset - this.minH.rzsl,
active: true, highDist: this.maxH.rzsl - offset
value: this.offsetToValue(offset), };
difference: this.scope.rzSliderHigh - this.scope.rzSliderModel, this.minH.addClass('rz-active');
offset: offset, this.maxH.addClass('rz-active');
lowDist: offset - this.minH.rzsl,
highDist: this.maxH.rzsl - offset this.onStart(pointer, ref, event);
}; },
this.minH.addClass('rz-active');
this.maxH.addClass('rz-active'); /**
* onDragMove event handler
this.onStart(pointer, ref, event); *
}, * Handles dragging of the middle bar.
*
/** * @param {jqLite} pointer
* onDragMove event handler * @param {Event} event The event
* * @returns {undefined}
* Handles dragging of the middle bar. */
* onDragMove: function(pointer, event) {
* @param {jqLite} pointer var newOffset = this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth,
* @param {Event} event The event
* @returns {undefined}
*/
onDragMove: function(pointer, event)
{
var newOffset = this.getEventX(event) - this.sliderElem.rzsl - this.handleHalfWidth,
newMinOffset, newMaxOffset, newMinOffset, newMaxOffset,
newMinValue, newMaxValue; newMinValue, newMaxValue;
if (newOffset <= this.dragging.lowDist) if (newOffset <= this.dragging.lowDist) {
{ if (pointer.rzsl === this.dragging.lowDist) {
if (pointer.rzsl === this.dragging.lowDist) { return; } return;
newMinValue = this.minValue; }
newMinOffset = 0; newMinValue = this.minValue;
newMaxValue = this.dragging.difference; newMinOffset = 0;
newMaxOffset = this.valueToOffset(newMaxValue); newMaxValue = this.minValue + this.dragging.difference;
} newMaxOffset = this.valueToOffset(newMaxValue);
else if (newOffset >= this.maxLeft - this.dragging.highDist) } else if (newOffset >= this.maxLeft - this.dragging.highDist) {
{ if (pointer.rzsl === this.dragging.highDist) {
if (pointer.rzsl === this.dragging.highDist) { return; } return;
newMaxValue = this.maxValue; }
newMaxOffset = this.maxLeft; newMaxValue = this.maxValue;
newMinValue = this.maxValue - this.dragging.difference; newMaxOffset = this.maxLeft;
newMinOffset = this.valueToOffset(newMinValue); newMinValue = this.maxValue - this.dragging.difference;
} newMinOffset = this.valueToOffset(newMinValue);
else } else {
{ newMinValue = this.offsetToValue(newOffset - this.dragging.lowDist);
newMinValue = this.offsetToValue(newOffset - this.dragging.lowDist); newMinValue = this.roundStep(newMinValue);
newMinValue = this.roundStep(newMinValue); newMinOffset = this.valueToOffset(newMinValue);
newMinOffset = this.valueToOffset(newMinValue); newMaxValue = newMinValue + this.dragging.difference;
newMaxValue = newMinValue + this.dragging.difference; newMaxOffset = this.valueToOffset(newMaxValue);
newMaxOffset = this.valueToOffset(newMaxValue); }
}
this.positionTrackingBar(newMinValue, newMaxValue, newMinOffset, newMaxOffset); this.positionTrackingBar(newMinValue, newMaxValue, newMinOffset, newMaxOffset);
}, },
/** /**
* Set the new value and offset for the entire bar * Set the new value and offset for the entire bar
* *
* @param {number} newMinValue the new minimum value * @param {number} newMinValue the new minimum value
* @param {number} newMaxValue the new maximum value * @param {number} newMaxValue the new maximum value
* @param {number} newMinOffset the new minimum offset * @param {number} newMinOffset the new minimum offset
* @param {number} newMaxOffset the new maximum offset * @param {number} newMaxOffset the new maximum offset
*/ */
positionTrackingBar: function(newMinValue, newMaxValue, newMinOffset, newMaxOffset) positionTrackingBar: function(newMinValue, newMaxValue, newMinOffset, newMaxOffset) {
{ this.scope.rzSliderModel = newMinValue;
this.scope.rzSliderModel = newMinValue; this.scope.rzSliderHigh = newMaxValue;
this.scope.rzSliderHigh = newMaxValue; this.updateHandles('rzSliderModel', newMinOffset);
this.updateHandles('rzSliderModel', newMinOffset); this.updateHandles('rzSliderHigh', newMaxOffset);
this.updateHandles('rzSliderHigh', newMaxOffset); this.scope.$apply();
this.scope.$apply(); this.callOnChange();
this.callOnChange(); },
},
/** /**
* Set the new value and offset to the current tracking handle * Set the new value and offset to the current tracking handle
* *
* @param {number} newValue new model value * @param {number} newValue new model value
* @param {number} newOffset new offset value * @param {number} newOffset new offset value
*/ */
positionTrackingHandle: function(newValue, newOffset) positionTrackingHandle: function(newValue, newOffset) {
{ if (this.range) {
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 is to check if we need to switch the min and max handles*/ this.scope[this.tracking] = this.scope.rzSliderHigh;
if (this.tracking === 'rzSliderModel' && newValue >= this.scope.rzSliderHigh) this.updateHandles(this.tracking, this.maxH.rzsl);
{ this.tracking = 'rzSliderHigh';
this.scope[this.tracking] = this.scope.rzSliderHigh; this.minH.removeClass('rz-active');
this.updateHandles(this.tracking, this.maxH.rzsl); this.maxH.addClass('rz-active');
this.tracking = 'rzSliderHigh'; /* We need to apply here because we are not sure that we will enter the next block */
this.minH.removeClass('rz-active'); this.scope.$apply();
this.maxH.addClass('rz-active'); this.callOnChange();
/* We need to apply here because we are not sure that we will enter the next block */ } else if (this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel) {
this.scope.$apply(); this.scope[this.tracking] = this.scope.rzSliderModel;
this.callOnChange(); 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.callOnChange();
}
} }
else if(this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel)
{ if (this.scope[this.tracking] !== newValue) {
this.scope[this.tracking] = this.scope.rzSliderModel; this.scope[this.tracking] = newValue;
this.updateHandles(this.tracking, this.minH.rzsl); this.updateHandles(this.tracking, newOffset);
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();
} }
} },
if(this.scope[this.tracking] !== newValue) /**
{ * onEnd event handler
this.scope[this.tracking] = newValue; *
this.updateHandles(this.tracking, newOffset); * @param {Event} event The event
this.scope.$apply(); * @param {Function} ehMove The the bound move event handler
this.callOnChange(); * @returns {undefined}
} */
}, onEnd: function(ehMove, event) {
var moveEventName = this.getEventNames(event).moveEvent;
/** this.minH.removeClass('rz-active');
* onEnd event handler this.maxH.removeClass('rz-active');
*
* @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'); $document.off(moveEventName, ehMove);
this.maxH.removeClass('rz-active');
$document.off(moveEventName, ehMove); this.scope.$emit('slideEnded');
this.tracking = '';
this.scope.$emit('slideEnded'); this.dragging.active = false;
this.tracking = ''; this.callOnEnd();
},
this.dragging.active = false; /**
this.callOnEnd(); * Get event names for move and event end
}, *
* @param {Event} event The event
*
* @return {{moveEvent: string, endEvent: string}}
*/
getEventNames: function(event) {
var eventNames = {
moveEvent: '',
endEvent: ''
};
if (event.touches || (event.originalEvent !== undefined && event.originalEvent.touches)) {
eventNames.moveEvent = 'touchmove';
eventNames.endEvent = 'touchend';
} else {
eventNames.moveEvent = 'mousemove';
eventNames.endEvent = 'mouseup';
}
/** return eventNames;
* Get event names for move and event end
*
* @param {Event} event The event
*
* @return {{moveEvent: string, endEvent: string}}
*/
getEventNames: function(event)
{
var eventNames = {moveEvent: '', endEvent: ''};
if(event.touches || (event.originalEvent !== undefined && event.originalEvent.touches))
{
eventNames.moveEvent = 'touchmove';
eventNames.endEvent = 'touchend';
}
else
{
eventNames.moveEvent = 'mousemove';
eventNames.endEvent = 'mouseup';
} }
};
return eventNames; return Slider;
} })
};
.directive('rzslider', function(RzSlider) {
return Slider; 'use strict';
})
return {
.directive('rzslider', function(RzSlider) restrict: 'E',
{ scope: {
'use strict'; rzSliderModel: '=?',
rzSliderHigh: '=?',
return { rzSliderOptions: '=?',
restrict: 'E', rzSliderTplUrl: '@'
scope: { },
rzSliderFloor: '=?',
rzSliderCeil: '=?', /**
rzSliderStep: '@', * Return template URL
rzSliderPrecision: '@', *
rzSliderModel: '=?', * @param {jqLite} elem
rzSliderHigh: '=?', * @param {Object} attrs
rzSliderDraggable: '@', * @return {string}
rzSliderTranslate: '&', */
rzSliderHideLimitLabels: '=?', templateUrl: function(elem, attrs) {
rzSliderAlwaysShowBar: '=?', //noinspection JSUnresolvedVariable
rzSliderPresentOnly: '@', return attrs.rzSliderTplUrl || 'rzSliderTpl.html';
rzSliderOnStart: '&', },
rzSliderOnChange: '&',
rzSliderOnEnd: '&',
rzSliderShowTicks: '=?',
rzSliderShowTicksValue: '=?',
rzSliderDisabled: '=?',
rzSliderInterval: '=?',
},
/**
* 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, attr) link: function(scope, elem) {
{ return new RzSlider(scope, elem);
return new RzSlider(scope, elem, attr); }
} };
}; });
});
// IDE assist // IDE assist
/** /**
* @name ngScope * @name ngScope
* *
* @property {number} rzSliderModel * @property {number} rzSliderModel
* @property {number} rzSliderHigh * @property {number} rzSliderHigh
* @property {number} rzSliderCeil * @property {Object} rzSliderOptions
*/ */
/** /**
* @name jqLite * @name jqLite
* *
* @property {number|undefined} rzsl rzslider label left offset * @property {number|undefined} rzsl rzslider label left offset
* @property {number|undefined} rzsw rzslider element width * @property {number|undefined} rzsw rzslider element width
* @property {string|undefined} rzsv rzslider label value/text * @property {string|undefined} rzsv rzslider label value/text
* @property {Function} css * @property {Function} css
* @property {Function} text * @property {Function} text
*/ */
/** /**
* @name Event * @name Event
* @property {Array} touches * @property {Array} touches
* @property {Event} originalEvent * @property {Event} originalEvent
*/ */
/** /**
* @name ThrottleOptions * @name ThrottleOptions
* *
* @property {boolean} leading * @property {boolean} leading
* @property {boolean} trailing * @property {boolean} trailing
*/ */
/*templateReplacement*/ /*templateReplacement*/
......
...@@ -14,7 +14,7 @@ rzslider { ...@@ -14,7 +14,7 @@ rzslider {
position: relative; position: relative;
height: @barHeight; height: @barHeight;
width: 100%; width: 100%;
margin: 30px 0 15px 0; margin: 35px 0 15px 0;
vertical-align: middle; vertical-align: middle;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
...@@ -49,6 +49,9 @@ rzslider { ...@@ -49,6 +49,9 @@ rzslider {
width: 100%; width: 100%;
height: @handleSize; height: @handleSize;
z-index: 1; z-index: 1;
&.rz-draggable {
cursor: move;
}
} }
.rz-bar { .rz-bar {
...@@ -140,4 +143,4 @@ rzslider { ...@@ -140,4 +143,4 @@ rzslider {
} }
} }
} }
} }
\ No newline at end of file
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