Commit 8015f5bd authored by Rafal Zajac's avatar Rafal Zajac

- Refoactor

- Performance
- Cleanup
- Documentation
parent 2fe464dd
......@@ -2,23 +2,140 @@
Slider directive implementation for AngularJS, without any dependencies.
## Example
- Mobile friendly
- Fast
- Well documented
- Customizable
- Simple to use
## Examples
### Single slider
```javascript
// In your controller
$scope.priceSlider = 150;
```
```html
<div>
<rzslider rz-slider-model="priceSlider"></rzslider>
</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.
```html
<div>
<rzslider
rz-slider-model="priceSlider"
rz-slider-ceil="450"></rzslider>
<!-- OR -->
<rzslider
rz-slider-model="priceSlider"
rz-slider-floor="0"
rz-slider-ceil="450"></rzslider>
</div>
```
### Range slider
```javascript
// In your controller
$scope.priceSlider = {
min: 100,
max: 180,
ceil: 500,
floor: 0
};
```
```html
<rzslider
rz-slider-floor="priceSlider.floor"
rz-slider-ceil="priceSlider.ceil"
rz-slider-low="priceSlider.min"
rz-slider-model="priceSlider.min"
rz-slider-high="priceSlider.max"></rzslider>
```
## Directive attributes
**rz-slider-model**
> Model for low value slider. If only _rz-slider-model_ is provided single slider will be rendered.
**rz-slider-high**
> Model for high value slider. Providing both _rz-slider-high_ and _rz-slider-model_ 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-translate**
> 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:
```javascript
// In your controller
$scope.priceSlider = {
min: 100,
max: 180,
ceil: 500,
floor: 0
};
$scope.translate = function(value)
{
return '$' + value;
}
```
```html
<rzslider
rz-slider-floor="priceSlider.floor"
rz-slider-ceil="priceSlider.ceil"
rz-slider-model="priceSlider.min"
rz-slider-high="priceSlider.max"
rz-slider-step="5"></rzslider>
</div>
rz-slider-translate="translate"></rzslider>
```
## Plunkers
## Plunker example
[http://embed.plnkr.co/EqGIlU/preview](http://embed.plnkr.co/EqGIlU/preview)
## Changelog
**v0.0.1**
Original rewrite to JavaScript
**v0.1.0**
Bug fixes
Performance improvements
Reduce number of angular bindings
Reduce number of function calls in event handlers
Avoid recalculate style
Hit 60fps
LESS variables for easier slider color customization
## 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
......
......@@ -11,15 +11,20 @@
<body ng-controller="MainCtrl">
<div style="background-color: #808080;margin-left: 50px;margin-right: 50px; padding: 30px;">
<div style="background-color: #808080;margin-left: 40px;margin-right: 50px; padding: 30px;">
<pre>{{ priceSlider | json }}</pre>
<input type="text" ng-model="priceSlider.min"/><br/>
<input type="text" ng-model="priceSlider.max"/><br/>
<br/>
<rzslider
rz-slider-floor="priceSlider.floor"
rz-slider-ceil="priceSlider.ceil"
rz-slider-low="priceSlider.min"
rz-slider-model="priceSlider.min"
rz-slider-high="priceSlider.max"
rz-slider-step="5"></rzslider>
rz-slider-step="1"></rzslider>
<br> <br> <br>
<pre>{{ priceSlider2 | json }}</pre>
......@@ -30,6 +35,12 @@
rz-slider-model="priceSlider2"
rz-slider-translate="translate"></rzslider>
<pre>{{ priceSlider2 | json }}</pre>
<br> <br>
<rzslider rz-slider-model="priceSlider3"
rz-slider-floor="50"
rz-slider-ceil="450"></rzslider>
</div>
</body>
......@@ -43,13 +54,14 @@
app.controller('MainCtrl', function($scope)
{
$scope.priceSlider = {
min: 0,
max: 300,
min: 4,
max: 481,
ceil: 500,
floor: 0
};
$scope.priceSlider2 = 100;
$scope.priceSlider2 = 150;
$scope.priceSlider3 = 250;
$scope.translate = function(value)
{
......
......@@ -47,7 +47,7 @@ rzslider span.pointer {
width: 32px;
height: 32px;
cursor: pointer;
background-color: #fff;
background-color: #ffffff;
-webkit-border-radius: 16px;
-moz-border-radius: 16px;
border-radius: 16px;
......@@ -86,6 +86,5 @@ rzslider span.bubble.selection {
}
rzslider span.bubble.limit {
/*color: #808080;*/
color: #67b700;
}
\ No newline at end of file
/*! angularjs-slider - v0.0.1 - (c) Rafal Zajac <rzajac@gmail.com>, https://github.com/rzajac/angularjs-slider.git - 2013-12-12 */
rzslider{position:relative;display:inline-block;width:100%;height:2px;margin:30px 0 15px 0;vertical-align:middle}rzslider span{position:absolute;display:inline-block;white-space:nowrap}rzslider span.base{width:100%;height:100%;padding:0}rzslider span.bar{z-index:0;width:100%;height:100%;background:#fff}rzslider span.bar.selection{z-index:1;width:0;background:#67b700}rzslider span.pointer{top:-15px;z-index:2;width:32px;height:32px;cursor:pointer;background-color:#fff;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px}rzslider span.pointer:after{position:absolute;top:12px;left:12px;width:8px;height:8px;background:#71818e;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;content:''}rzslider span.pointer:hover:after{background-color:#67b700}rzslider span.pointer.active:after{background-color:#67b700}rzslider span.bubble{top:-32px;padding:1px 3px 1px 3px;color:#67b700;cursor:default}rzslider span.bubble.selection{top:15px}
\ No newline at end of file
/*! angularjs-slider - v0.1.0 - (c) Rafal Zajac <rzajac@gmail.com>, https://github.com/rzajac/angularjs-slider.git - 2013-12-13 */
rzslider{position:relative;display:inline-block;width:100%;height:2px;margin:30px 0 15px 0;vertical-align:middle}rzslider span{position:absolute;display:inline-block;white-space:nowrap}rzslider span.base{width:100%;height:100%;padding:0}rzslider span.bar{z-index:0;width:100%;height:100%;background:#fff}rzslider span.bar.selection{z-index:1;width:0;background:#67b700}rzslider span.pointer{top:-15px;z-index:2;width:32px;height:32px;cursor:pointer;background-color:#fff;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px}rzslider span.pointer:after{position:absolute;top:12px;left:12px;width:8px;height:8px;background:#71818e;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;content:''}rzslider span.pointer:hover:after{background-color:#67b700}rzslider span.pointer.active:after{background-color:#67b700}rzslider span.bubble{top:-32px;padding:1px 3px 1px 3px;color:#67b700;cursor:default}rzslider span.bubble.selection{top:15px}rzslider span.bubble.limit{color:#67b700}
\ No newline at end of file
/*! angularjs-slider - v0.0.1 - (c) Rafal Zajac <rzajac@gmail.com>, https://github.com/rzajac/angularjs-slider.git - 2013-12-12 */
angular.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("Slider",["$timeout","$document","throttle",function(a,b,c){var d=function(a,b,c){this.scope=a,this.attributes=c,this.element=b,this.range=void 0===c.rzSliderModel&&void 0!==c.rzSliderLow&&void 0!==c.rzSliderHigh,this.refLow=this.range?"rzSliderLow":"rzSliderModel",this.refHigh="rzSliderHigh",this.barWidth=0,this.ptrHalfWidth=0,this.minOffset=0,this.maxOffset=0,this.minValue=0,this.maxValue=0,this.highValOffsetPerc=0,this.lowValOffsetPerc=0,this.valueRange=0,this.offsetRange=0,this.precision=0,this.step=0,this.tracking="",this.fullBar=null,this.selBar=null,this.minPtr=null,this.maxPtr=null,this.selLab=null,this.flrLab=null,this.ceilLab=null,this.lowLab=null,this.highLab=null,this.cmbLab=null,this.init()};return d.prototype={init:function(){var b=this;void 0===this.attributes.rzSliderTranslate&&(this.scope.rzSliderTranslate=function(a){return a.value}),this.setMinAndMax(),this.valueRange=this.maxValue-this.minValue,this.precision=void 0===this.scope.rzSliderPrecision?0:+this.scope.rzSliderPrecision,this.step=void 0===this.scope.rzSliderStep?1:+this.scope.rzSliderStep,this.cacheElemHandles(),this.calcViewDimensions(),a(function(){b.updateHandles("both"),b.adjustLabels("timeout"),b.bindToInputEvents()}),angular.element(window).on("resize",angular.bind(this,this.calcViewDimensions)),this.scope.$watch(this.refLow,function(){b.updateHandles("low"),b.adjustLabels("refLow")}),this.range&&this.scope.$watch(this.refHigh,function(){b.updateHandles("high"),b.adjustLabels("refHigh")}),void 0!==this.scope.rzSliderFloor&&this.scope.$watch("rzSliderFloor",function(){b.setMinAndMax(),b.updateHandles("both"),b.adjustLabels("floor")}),void 0!==this.scope.rzSliderCeil&&this.scope.$watch("rzSliderCeil",function(){b.setMinAndMax(),b.updateHandles("both"),b.adjustLabels("ceil")})},setMinAndMax:function(){this.minValue=void 0===this.scope.rzSliderFloor?0:+this.scope.rzSliderFloor,this.maxValue=void 0===this.scope.rzSliderCeil?this.range?this.scope[this.refHigh]:this.scope[this.refLow]:+this.scope.rzSliderCeil},cacheElemHandles:function(){angular.forEach(this.element.children(),function(a,b){var c=angular.element(a);switch(b){case 0:this.fullBar=c;break;case 1:this.selBar=c;break;case 2:this.minPtr=c;break;case 3:this.maxPtr=c;break;case 4:this.selLab=c;break;case 5:this.flrLab=c;break;case 6:this.ceilLab=c;break;case 7:this.lowLab=c;break;case 8:this.highLab=c;break;case 9:this.cmbLab=c}},this),this.range||(this.cmbLab.remove(),this.highLab.remove(),this.maxPtr.remove(),this.selBar.remove(),this.selLab.remove())},calcViewDimensions:function(){var a=this.offsetWidth(this.minPtr);this.ptrHalfWidth=a/2,this.barWidth=this.offsetWidth(this.fullBar),this.minOffset=0,this.maxOffset=this.barWidth-a,this.offsetRange=this.maxOffset-this.minOffset,this.setLeft(this.ceilLab,this.barWidth-this.offsetWidth(this.ceilLab))},updateHandles:function(a){return"low"===a?(this.updateLowHandle(),this.range&&this.updateSelectionBar(),void 0):"high"===a?(this.updateHighHandle(),this.range&&this.updateSelectionBar(),void 0):(this.updateLowHandle(),this.updateHighHandle(),this.updateSelectionBar(),void 0)},updateLowHandle:function(){var a;this.lowValOffsetPerc=this.percentValue(this.scope[this.refLow]),a=this.setLeft(this.minPtr,this.percentToOffset(this.lowValOffsetPerc)),this.setLeft(this.lowLab,a-this.halfOffsetWidth(this.lowLab)+this.ptrHalfWidth)},updateHighHandle:function(){var a;this.highValOffsetPerc=this.percentValue(this.scope[this.refHigh]),a=this.setLeft(this.maxPtr,this.percentToOffset(this.highValOffsetPerc)),this.setLeft(this.highLab,a-this.halfOffsetWidth(this.highLab)+this.ptrHalfWidth)},updateSelectionBar:function(){var a,b;a=this.setLeft(this.selBar,this.percentToOffset(this.lowValOffsetPerc)+this.ptrHalfWidth),b=this.percentToOffset(this.highValOffsetPerc-this.lowValOffsetPerc),this.selBar.css({width:b+"px"}),this.setLeft(this.cmbLab,a+b/2-this.halfOffsetWidth(this.cmbLab)+1),this.setLeft(this.selLab,a+b/2-this.halfOffsetWidth(this.selLab)+1),this.scope.rzSliderDiff=this.roundStep(this.scope[this.refHigh]-this.scope[this.refLow])},adjustLabels:function(){var a=this.highLab;this.fitToBar(this.lowLab),this.range&&(this.fitToBar(this.highLab),this.fitToBar(this.selLab),this.gap(this.lowLab,this.highLab)<10?(this.hideEl(this.lowLab),this.hideEl(this.highLab),this.fitToBar(this.cmbLab),this.showEl(this.cmbLab),a=this.cmbLab):(this.showEl(this.lowLab),this.showEl(this.highLab),this.hideEl(this.cmbLab),a=this.highLab)),this.gap(this.flrLab,this.lowLab)<5?this.hideEl(this.flrLab):this.range?this.gap(this.flrLab,a)<5?this.hideEl(this.flrLab):this.showEl(this.flrLab):this.showEl(this.flrLab),this.gap(this.lowLab,this.ceilLab)<5?this.hideEl(this.ceilLab):this.range?this.gap(a,this.ceilLab)<5?this.hideEl(this.ceilLab):this.showEl(this.ceilLab):this.showEl(this.ceilLab),"timeout"===arguments[0]&&(d.prototype.adjustLabels=c(d.prototype.adjustLabels,350))},roundStep:function(a){var b=this.step,c=Math.pow(10,this.precision),d=(a-this.minValue)%b,e=d>b/2?a+b-d:a-d;return+(e*c/c).toFixed(this.precision)},hideEl:function(a){return a.css({opacity:0})},showEl:function(a){return a.css({opacity:1})},offsetLeft:function(a){return a[0].offsetLeft},offsetWidth:function(a){return a[0].offsetWidth},halfOffsetWidth:function(a){return a[0].offsetWidth/2},setLeft:function(a,b){return a.css({left:b+"px"}),b},fitToBar:function(a){this.setLeft(a,Math.min(Math.max(0,this.offsetLeft(a)),this.barWidth-this.offsetWidth(a)))},gap:function(a,b){return this.offsetLeft(b)-this.offsetLeft(a)-this.offsetWidth(a)},percentValue:function(a){return(a-this.minValue)/this.valueRange*100},percentOffset:function(a){return(a-this.minOffset)/this.offsetRange*100},percentToOffset:function(a){return a*this.offsetRange/100},bindToInputEvents:function(){this.minPtr.on("mousedown",angular.bind(this,this.onStart,this.minPtr,this.refLow)),this.range&&this.maxPtr.on("mousedown",angular.bind(this,this.onStart,this.maxPtr,this.refHigh)),this.minPtr.on("touchstart",angular.bind(this,this.onStart,this.minPtr,this.refLow)),this.range&&this.maxPtr.on("touchstart",angular.bind(this,this.onStart,this.maxPtr,this.refHigh))},onStart:function(a,c,d){var e;if(""===this.tracking){switch(this.tracking=c,c){case"rzSliderModel":case"rzSliderLow":e="low";break;case"rzSliderHigh":e="high"}a.addClass("active"),d.stopPropagation(),d.preventDefault(),d.touches?(b.on("touchmove",angular.bind(this,this.onMove,e)),b.on("touchend",angular.bind(this,this.onEnd,a))):(b.on("mousemove",angular.bind(this,this.onMove,e)),b.on("mouseup",angular.bind(this,this.onEnd,a)))}},onMove:function(a,b){var c,d,e,f=b.clientX||b.touches[0].clientX;c=f-this.element[0].getBoundingClientRect().left-this.ptrHalfWidth,c=Math.max(Math.min(c,this.maxOffset),this.minOffset),d=this.percentOffset(c),e=this.minValue+this.valueRange*d/100,this.range&&(this.tracking===this.refLow&&e>=this.scope[this.refHigh]?(this.scope[this.tracking]=this.scope[this.refHigh],this.tracking=this.refHigh,this.minPtr.removeClass("active"),this.maxPtr.addClass("active")):e<=this.scope[this.refLow]&&(this.scope[this.tracking]=this.scope[this.refLow],this.tracking=this.refLow,this.maxPtr.removeClass("active"),this.minPtr.addClass("active"))),this.scope[this.tracking]=this.roundStep(e),this.updateHandles(a),this.adjustLabels("onMove"),this.scope.$apply()},onEnd:function(a,c){a.removeClass("active"),c.touches?(b.unbind("touchmove"),b.unbind("touchend")):(b.unbind("mousemove"),b.unbind("mouseup")),this.tracking=""}},d}]).directive("rzslider",["Slider",function(a){return{restrict:"E",scope:{rzSliderFloor:"=?",rzSliderCeil:"=?",rzSliderStep:"@",rzSliderPrecision:"@",rzSliderModel:"=?",rzSliderLow:"=?",rzSliderHigh:"=?",rzSliderTranslate:"&"},template:'<span class="bar"></span><span class="bar selection"></span><span class="pointer"></span><span class="pointer"></span><span class="bubble selection"></span><span class="bubble limit" ng-bind="rzSliderTranslate({value: rzSliderFloor})"></span><span class="bubble limit" ng-bind="rzSliderTranslate({value: rzSliderCeil})" class="bubble limit"></span><span class="bubble"></span><span class="bubble"></span><span class="bubble"></span>',compile:function(b,c){var d=b.children(),e=void 0===c.rzSliderModel&&void 0!==c.rzSliderLow&&void 0!==c.rzSliderHigh,f=e?"rzSliderLow":"rzSliderModel",g="rzSliderHigh";return c.rzSliderTranslate&&c.$set("rzSliderTranslate",""+c.rzSliderTranslate+"(value)"),angular.element(d[4]).attr("ng-bind","rzSliderTranslate({value: rzSliderDiff})"),angular.element(d[7]).attr("ng-bind","rzSliderTranslate({value: "+f+"})"),angular.element(d[8]).attr("ng-bind","rzSliderTranslate({value: "+g+"})"),angular.element(d[9]).attr("ng-bind-html","rzSliderTranslate({value: "+f+'}) + " - " + rzSliderTranslate({value: '+g+"})"),{post:function(b,c,d){return new a(b,c,d)}}}}}]);
\ No newline at end of file
/*! angularjs-slider - v0.1.0 - (c) Rafal Zajac <rzajac@gmail.com>, https://github.com/rzajac/angularjs-slider.git - 2013-12-13 */
angular.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("Slider",["$timeout","$document","throttle",function(a,b,c){var d=function(a,b,c){this.scope=a,this.attributes=c,this.sliderElem=b,this.range=void 0!==c.rzSliderHigh&&void 0!==c.rzSliderModel,this.handleHalfWidth=0,this.maxLeft=0,this.precision=0,this.step=0,this.tracking="",this.minValue=0,this.maxValue=0,this.valueRange=0,this.initRun=!1,this.customTrFn=null,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.init()};return d.prototype={init:function(){var b=this;this.scope.rzSliderTranslate&&(this.customTrFn=this.scope.rzSliderTranslate()),this.initElemHandles(),this.calcViewDimensions(),this.setMinAndMax(),this.valueRange=this.maxValue-this.minValue,this.precision=void 0===this.scope.rzSliderPrecision?0:+this.scope.rzSliderPrecision,this.step=void 0===this.scope.rzSliderStep?1:+this.scope.rzSliderStep,a(function(){b.updateCeilLab(),b.updateFloorLab(),b.initHandles(),b.bindEvents()}),angular.element(window).on("resize",angular.bind(this,this.calcViewDimensions)),this.initRun=!0;var d=c(function(){b.updateLowHandle(b.valueToOffset(b.scope.rzSliderModel)),b.range&&(b.updateSelectionBar(),b.updateCmbLabel())},350,{leading:!1}),e=c(function(){b.updateHighHandle(b.valueToOffset(b.scope.rzSliderHigh)),b.updateSelectionBar(),b.updateCmbLabel()},350,{leading:!1});this.scope.$watch("rzSliderModel",function(a,b){a!==b&&d()}),this.scope.$watch("rzSliderHigh",function(a,b){a!==b&&e()})},initHandles:function(){this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel)),this.range&&(this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh)),this.updateSelectionBar(),this.updateCmbLabel())},translateFn:function(a,b){var c=this.customTrFn?""+this.customTrFn(a):""+a,d=!1;(void 0===b.rzsv||b.rzsv.length!=c.length)&&(d=!0,b.rzsv=c),b.text(c),d&&this.getWidth(b)},setMinAndMax:function(){this.minValue=this.scope.rzSliderFloor?+this.scope.rzSliderFloor:this.scope.rzSliderFloor=0,this.scope.rzSliderCeil?this.maxValue=+this.scope.rzSliderCeil:this.scope.rzSliderCeil=this.maxValue=this.range?this.scope.rzSliderHigh:this.scope.rzSliderModel},initElemHandles:function(){angular.forEach(this.sliderElem.children(),function(a,b){var c=angular.element(a);switch(b){case 0:this.fullBar=c;break;case 1:this.selBar=c;break;case 2:this.minH=c;break;case 3:this.maxH=c;break;case 4:this.flrLab=c;break;case 5:this.ceilLab=c;break;case 6:this.minLab=c;break;case 7:this.maxLab=c;break;case 8:this.cmbLab=c}},this),this.fullBar.rzsl=0,this.selBar.rzsl=0,this.minH.rzsl=0,this.maxH.rzsl=0,this.flrLab.rzsl=0,this.ceilLab.rzsl=0,this.minLab.rzsl=0,this.maxLab.rzsl=0,this.cmbLab.rzsl=0,this.range||(this.cmbLab.remove(),this.maxLab.remove(),this.maxH.remove(),this.selBar.remove())},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.initRun&&(this.updateCeilLab(),this.initHandles())},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)},updateHandles:function(a,b){return"rzSliderModel"===a?(this.updateLowHandle(b),this.range&&(this.updateSelectionBar(),this.updateCmbLabel()),void 0):"rzSliderHigh"===a?(this.updateHighHandle(b),this.range&&(this.updateSelectionBar(),this.updateCmbLabel()),void 0):(this.updateLowHandle(b),this.updateHighHandle(b),this.updateSelectionBar(),this.updateCmbLabel(),void 0)},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.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,this.maxH.rzsl-this.minH.rzsl),this.setLeft(this.selBar,this.minH.rzsl+this.handleHalfWidth)},updateCmbLabel:function(){this.minLab.rzsl+this.minLab.rzsw+10>=this.maxLab.rzsl?(this.translateFn(this.scope.rzSliderModel+" - "+this.scope.rzSliderHigh,this.cmbLab),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))},roundStep:function(a){var b=this.step,c=(a-this.minValue)%b,d=c>b/2?a+b-c:a-c;return+d.toFixed(this.precision)},hideEl:function(a){return a.css({opacity:0})},showEl:function(a){return 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},offsetToValue:function(a){return a/this.maxLeft*this.valueRange+this.minValue},bindEvents:function(){this.minH.on("mousedown",angular.bind(this,this.onStart,this.minH,"rzSliderModel")),this.range&&this.maxH.on("mousedown",angular.bind(this,this.onStart,this.maxH,"rzSliderHigh")),this.minH.on("touchstart",angular.bind(this,this.onStart,this.minH,"rzSliderModel")),this.range&&this.maxH.on("touchstart",angular.bind(this,this.onStart,this.maxH,"rzSliderHigh"))},onStart:function(a,c,d){d.stopPropagation(),d.preventDefault(),""===this.tracking&&(this.tracking=c,a.addClass("active"),d.touches?(b.on("touchmove",angular.bind(this,this.onMove,a)),b.on("touchend",angular.bind(this,this.onEnd))):(b.on("mousemove",angular.bind(this,this.onMove,a)),b.on("mouseup",angular.bind(this,this.onEnd))))},onMove:function(a,b){var c,d=b.clientX||b.touches[0].clientX,e=this.sliderElem.rzsl,f=d-e-this.handleHalfWidth;return 0>=f?(0!==a.rzsl&&(this.scope[this.tracking]=this.minValue,this.updateHandles(this.tracking,0),this.scope.$apply()),void 0):f>=this.maxLeft?(a.rzsl!==this.maxLeft&&(this.scope[this.tracking]=this.maxValue,this.updateHandles(this.tracking,this.maxLeft),this.scope.$apply()),void 0):(c=this.offsetToValue(f),c=this.roundStep(c),this.range&&("rzSliderModel"===this.tracking&&c>=this.scope.rzSliderHigh?(this.scope[this.tracking]=this.scope.rzSliderHigh,this.updateHandles(this.tracking,this.maxH.rzsl),this.tracking="rzSliderHigh",this.minH.removeClass("active"),this.maxH.addClass("active")):"rzSliderHigh"===this.tracking&&c<=this.scope.rzSliderModel&&(this.scope[this.tracking]=this.scope.rzSliderModel,this.updateHandles(this.tracking,this.minH.rzsl),this.tracking="rzSliderModel",this.maxH.removeClass("active"),this.minH.addClass("active"))),this.scope[this.tracking]!==c&&(this.scope[this.tracking]=c,this.updateHandles(this.tracking,f),this.scope.$apply()),void 0)},onEnd:function(a){this.minH.removeClass("active"),this.maxH.removeClass("active"),a.touches?(b.unbind("touchmove"),b.unbind("touchend")):(b.unbind("mousemove"),b.unbind("mouseup")),this.tracking=""}},d}]).directive("rzslider",["Slider",function(a){return{restrict:"E",scope:{rzSliderFloor:"=?",rzSliderCeil:"=?",rzSliderStep:"@",rzSliderPrecision:"@",rzSliderModel:"=?",rzSliderHigh:"=?",rzSliderTranslate:"&"},template:'<span class="bar"></span><span class="bar selection"></span><span class="pointer"></span><span class="pointer"></span><span class="bubble limit"></span><span class="bubble limit"></span><span class="bubble"></span><span class="bubble"></span><span class="bubble"></span>',link:function(b,c,d){return new a(b,c,d)}}}]);
\ No newline at end of file
{
"name": "angularjs-slider",
"version": "0.0.1",
"version": "0.1.0",
"description": "Slider directive for AngularJS. No dependencies.",
"main": "rzslider.js",
"repository": {
......
......@@ -4,6 +4,8 @@
* (c) Rafal Zajac <rzajac@gmail.com>
* http://github.com/rzajac/angularjs-slider
*
* Version: v0.0.1
*
* Licensed under the MIT license
*/
......@@ -51,17 +53,17 @@ function throttle(func, wait, options) {
/**
* Slider
*
* @param {*} scope The AngularJS scope
* @param {Element} element The slider directive element wrapped in jqLite
* @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, element, attributes)
var Slider = function(scope, sliderElem, attributes)
{
/**
* The slider's scope
*
* @type {*}
* @type {ngScope}
*/
this.scope = scope;
......@@ -77,56 +79,49 @@ function throttle(func, wait, options) {
*
* @type {jqLite}
*/
this.element = element;
this.sliderElem = sliderElem;
/**
* Slider type
*
* @type {string}
*/
this.range = (attributes.rzSliderModel === undefined) && ((attributes.rzSliderLow !== undefined) && (attributes.rzSliderHigh !== undefined));
this.range = attributes.rzSliderHigh !== undefined && attributes.rzSliderModel !== undefined;
/**
* Name of the low value model
*
* @type {string}
*/
this.refLow = this.range ? 'rzSliderLow' : 'rzSliderModel';
/**
* Name of the high value model
* Half of the width of the slider handles
*
* @type {string}
* @type {number}
*/
this.refHigh = 'rzSliderHigh';
this.handleHalfWidth = 0;
/**
* The total width of slider bar
* Maximum left the slider handle can have
*
* @type {number}
*/
this.barWidth = 0;
this.maxLeft = 0;
/**
* Half of the width of the slider handles
* Precision
*
* @type {number}
*/
this.ptrHalfWidth = 0;
this.precision = 0;
/**
* Minimum left offset the slider handle can have
* Step
*
* @type {number}
*/
this.minOffset = 0;
this.step = 0;
/**
* Maximum left offset the slider handle can have
* The name of the handle we are currently tracking
*
* @type {number}
* @type {string}
*/
this.maxOffset = 0;
this.tracking = '';
/**
* Minimum value (floor) of the model
......@@ -142,20 +137,6 @@ function throttle(func, wait, options) {
*/
this.maxValue = 0;
/**
* High value handle offset in percentages
*
* @type {number}
*/
this.highValOffsetPerc = 0;
/**
* Low value handle offset in percentages
*
* @type {number}
*/
this.lowValOffsetPerc = 0;
/**
* The delta between min and max value
*
......@@ -164,43 +145,28 @@ function throttle(func, wait, options) {
this.valueRange = 0;
/**
* The delta between min and max left offset
* Set to true if init method already executed
*
* @type {number}
* @type {boolean}
*/
this.offsetRange = 0;
this.initRun = false;
/**
* Precision
* Custom translate function
*
* @type {number}
* @type {function}
*/
this.precision = 0;
/**
* Step
*
* @type {number}
*/
this.step = 0;
/**
* The name of the handle we are currently tracking
*
* @type {string}
*/
this.tracking = '';
this.customTrFn = null;
// Slider DOM elements wrapped in jqLite
this.fullBar = null; // The whole slider bar
this.selBar = null; // Highlight between two handles
this.minPtr = null; // Left slider handle
this.maxPtr = null; // Right slider handle
this.selLab = null; // Range label
this.minH = null; // Left slider handle
this.maxH = null; // Right slider handle
this.flrLab = null; // Floor label
this.ceilLab = null; // Ceiling label
this.lowLab = null; // Label above the low value
this.highLab = null; // Label above the high value
this.minLab = null; // Label above the low value
this.maxLab = null; // Label above the high value
this.cmbLab = null; // Combined label
// Initialize slider
......@@ -219,71 +185,107 @@ function throttle(func, wait, options) {
{
var self = this;
// Provide default translate function if needed
if (this.attributes.rzSliderTranslate === undefined)
if(this.scope.rzSliderTranslate)
{
this.scope.rzSliderTranslate = function (value)
{
return value.value;
};
this.customTrFn = this.scope.rzSliderTranslate();
}
this.initElemHandles();
this.calcViewDimensions();
this.setMinAndMax();
this.valueRange = this.maxValue - this.minValue;
this.precision = this.scope.rzSliderPrecision === undefined ? 0 : +this.scope.rzSliderPrecision;
this.step = this.scope.rzSliderStep === undefined ? 1 : +this.scope.rzSliderStep;
this.cacheElemHandles();
this.calcViewDimensions();
$timeout(function()
{
self.updateHandles('both');
self.adjustLabels('timeout');
self.bindToInputEvents();
self.updateCeilLab();
self.updateFloorLab();
self.initHandles();
self.bindEvents();
});
// Recalculate stuff if view port dimensions have changed
angular.element(window).on('resize', angular.bind(this, this.calcViewDimensions));
this.initRun = true;
// Watch for changes to the model
this.scope.$watch(this.refLow, function()
{
self.updateHandles('low');
self.adjustLabels('refLow');
});
if(this.range)
var thrLow = throttle(function()
{
// We have to watch it only for range slider
this.scope.$watch(this.refHigh, function()
self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));
if(self.range)
{
self.updateHandles('high');
self.adjustLabels('refHigh');
});
self.updateSelectionBar();
self.updateCmbLabel();
}
// We do not have to watch it if it was not defined
if(this.scope.rzSliderFloor !== undefined)
}, 350, { leading: false });
var thrHigh = throttle(function()
{
self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
self.updateSelectionBar();
self.updateCmbLabel();
}, 350, { leading: false });
this.scope.$watch('rzSliderModel', function(newValue, oldValue)
{
this.scope.$watch('rzSliderFloor', function()
if(newValue === oldValue) return;
thrLow();
});
this.scope.$watch('rzSliderHigh', function(newValue, oldValue)
{
self.setMinAndMax();
self.updateHandles('both');
self.adjustLabels('floor');
if(newValue === oldValue) return;
thrHigh();
});
},
/**
* Initialize slider handles positions and labels
*
* Run only once during initialization and every time view port changes size
*
* @returns {undefined}
*/
initHandles: function()
{
this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel));
if(this.range)
{
this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh));
this.updateSelectionBar();
this.updateCmbLabel();
}
},
// We do not have to watch it if it was not defined
if(this.scope.rzSliderCeil !== undefined)
/**
* Translate value to human readable format
*
* @param {number|string} value
* @param {jqLite} label
* @returns {undefined}
*/
translateFn: function(value, label)
{
this.scope.$watch('rzSliderCeil', function()
var valStr = this.customTrFn ? '' + this.customTrFn(value) : '' + value,
getWidth = false;
if(label.rzsv === undefined || label.rzsv.length != valStr.length)
{
self.setMinAndMax();
self.updateHandles('both');
self.adjustLabels('ceil');
});
getWidth = true;
label.rzsv = valStr;
}
label.text(valStr);
// Update width only when length of the label have changed
if(getWidth) { this.getWidth(label); }
},
/**
......@@ -293,26 +295,35 @@ function throttle(func, wait, options) {
*/
setMinAndMax: function()
{
this.minValue = this.scope.rzSliderFloor === undefined ? 0 : +this.scope.rzSliderFloor;
if(this.scope.rzSliderCeil === undefined)
if(this.scope.rzSliderFloor)
{
this.maxValue = this.range ? this.scope[this.refHigh] : this.scope[this.refLow];
this.minValue = +this.scope.rzSliderFloor;
}
else
{
this.minValue = this.scope.rzSliderFloor = 0;
}
if(this.scope.rzSliderCeil)
{
this.maxValue = +this.scope.rzSliderCeil;
}
else
{
this.scope.rzSliderCeil = this.maxValue = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
}
},
/**
* Set the slider children to variables for easy access
*
* Run only once during initialization
*
* @returns {undefined}
*/
cacheElemHandles: function()
initElemHandles: function()
{
angular.forEach(this.element.children(), function(elem, index)
angular.forEach(this.sliderElem.children(), function(elem, index)
{
var _elem = angular.element(elem);
......@@ -320,210 +331,236 @@ function throttle(func, wait, options) {
{
case 0: this.fullBar = _elem; break;
case 1: this.selBar = _elem; break;
case 2: this.minPtr = _elem; break;
case 3: this.maxPtr = _elem; break;
case 4: this.selLab = _elem; break;
case 5: this.flrLab = _elem; break;
case 6: this.ceilLab = _elem; break;
case 7: this.lowLab = _elem; break;
case 8: this.highLab = _elem; break;
case 9: this.cmbLab = _elem; break;
case 2: this.minH = _elem; break;
case 3: this.maxH = _elem; break;
case 4: this.flrLab = _elem; break;
case 5: this.ceilLab = _elem; break;
case 6: this.minLab = _elem; break;
case 7: this.maxLab = _elem; break;
case 8: this.cmbLab = _elem; break;
}
}, this);
// Initialize offsets
this.fullBar.rzsl = 0;
this.selBar.rzsl = 0;
this.minH.rzsl = 0;
this.maxH.rzsl = 0;
this.flrLab.rzsl = 0;
this.ceilLab.rzsl = 0;
this.minLab.rzsl = 0;
this.maxLab.rzsl = 0;
this.cmbLab.rzsl = 0;
// Remove stuff not needed in single slider
if( ! this.range)
{
this.cmbLab.remove();
this.highLab.remove();
this.maxPtr.remove();
this.maxLab.remove();
this.maxH.remove();
this.selBar.remove();
this.selLab.remove();
}
},
/**
* Calculate dimensions that are dependent on window size
* 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 pointerWidth = this.offsetWidth(this.minPtr);
var handleWidth = this.getWidth(this.minH);
this.handleHalfWidth = handleWidth / 2;
this.barWidth = this.getWidth(this.fullBar);
this.maxLeft = this.barWidth - handleWidth;
this.getWidth(this.sliderElem);
this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;
if(this.initRun)
{
this.updateCeilLab();
this.initHandles();
}
},
this.ptrHalfWidth = pointerWidth / 2;
this.barWidth = this.offsetWidth(this.fullBar);
this.minOffset = 0;
this.maxOffset = this.barWidth - pointerWidth;
this.offsetRange = this.maxOffset - this.minOffset;
this.setLeft(this.ceilLab, this.barWidth - this.offsetWidth(this.ceilLab));
/**
* Update position of the ceiling label
*
* @returns {undefined}
*/
updateCeilLab: function()
{
this.translateFn(this.scope.rzSliderCeil, this.ceilLab);
this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw);
this.getWidth(this.ceilLab);
},
/**
* Update position of the floor label
*
* @returns {undefined}
*/
updateFloorLab: function()
{
this.translateFn(this.scope.rzSliderFloor, this.flrLab);
this.getWidth(this.flrLab);
},
/**
* Update slider handles and label positions
*
* @param {string} which
* @param {number} newOffset
*/
updateHandles: function(which)
updateHandles: function(which, newOffset)
{
if(which === 'rzSliderModel')
{
if(which === 'low')
this.updateLowHandle(newOffset);
if(this.range)
{
this.updateLowHandle();
if(this.range) { this.updateSelectionBar(); }
this.updateSelectionBar();
this.updateCmbLabel();
}
return;
}
if(which === 'high')
if(which === 'rzSliderHigh')
{
this.updateHighHandle(newOffset);
if(this.range)
{
this.updateHighHandle();
if(this.range) { this.updateSelectionBar(); }
this.updateSelectionBar();
this.updateCmbLabel();
}
return;
}
// Update both
this.updateLowHandle();
this.updateHighHandle();
this.updateLowHandle(newOffset);
this.updateHighHandle(newOffset);
this.updateSelectionBar();
this.updateCmbLabel();
},
/**
* Update low slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateLowHandle: function()
updateLowHandle: function(newOffset)
{
var minPtrOL;
this.lowValOffsetPerc = this.percentValue(this.scope[this.refLow]);
// Set low value slider handle position
minPtrOL = this.setLeft(this.minPtr, this.percentToOffset(this.lowValOffsetPerc));
this.setLeft(this.minH, newOffset);
this.translateFn(this.scope.rzSliderModel, this.minLab);
this.setLeft(this.minLab, newOffset - this.minLab.rzsw / 2 + this.handleHalfWidth);
// Set low value label position
this.setLeft(this.lowLab, minPtrOL - this.halfOffsetWidth(this.lowLab) + this.ptrHalfWidth);
this.shFloorCeil();
},
/**
* Update high slider handle position and label
*
* @param {number} newOffset
* @returns {undefined}
*/
updateHighHandle: function()
{
var maxPtrOL;
this.highValOffsetPerc = this.percentValue(this.scope[this.refHigh]);
// Set high value slider handle position
maxPtrOL = this.setLeft(this.maxPtr, this.percentToOffset(this.highValOffsetPerc));
// Set high value slider handle label position
this.setLeft(this.highLab, maxPtrOL - (this.halfOffsetWidth(this.highLab)) + this.ptrHalfWidth);
},
/**
* Update slider selection bar, combined label and range label
*/
updateSelectionBar: function()
updateHighHandle: function(newOffset)
{
var selBarOL, selBarWidth;
// Set selection bar position
selBarOL = this.setLeft(this.selBar, this.percentToOffset(this.lowValOffsetPerc) + this.ptrHalfWidth);
selBarWidth = this.percentToOffset(this.highValOffsetPerc - this.lowValOffsetPerc);
this.selBar.css({width: selBarWidth + 'px'});
// Set combined label position
this.setLeft(this.cmbLab, selBarOL + selBarWidth / 2 - this.halfOffsetWidth(this.cmbLab) + 1);
this.setLeft(this.maxH, newOffset);
this.translateFn(this.scope.rzSliderHigh, this.maxLab);
this.setLeft(this.maxLab, newOffset - this.maxLab.rzsw / 2 + this.handleHalfWidth);
// Set range label position
this.setLeft(this.selLab, selBarOL + selBarWidth / 2 - this.halfOffsetWidth(this.selLab) + 1);
this.scope.rzSliderDiff = this.roundStep(this.scope[this.refHigh] - this.scope[this.refLow]);
this.shFloorCeil();
},
/**
* Adjust label positions and visibility
* Show / hide floor / ceiling label
*
* @returns {undefined}
*/
adjustLabels: function (origin)
shFloorCeil: function()
{
// console.log('al', this.scope.$id + ' ' + arguments[0]);
var bubToAdjust = this.highLab;
var flHidden = false, clHidden = false;
this.fitToBar(this.lowLab);
if (this.range)
if(this.minLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + 5)
{
this.fitToBar(this.highLab);
this.fitToBar(this.selLab);
if (this.gap(this.lowLab, this.highLab) < 10)
{
this.hideEl(this.lowLab);
this.hideEl(this.highLab);
this.fitToBar(this.cmbLab);
this.showEl(this.cmbLab);
bubToAdjust = this.cmbLab;
flHidden = true;
this.hideEl(this.flrLab);
}
else
{
this.showEl(this.lowLab);
this.showEl(this.highLab);
this.hideEl(this.cmbLab);
bubToAdjust = this.highLab;
}
flHidden = false;
this.showEl(this.flrLab);
}
if (this.gap(this.flrLab, this.lowLab) < 5)
if(this.minLab.rzsl + this.minLab.rzsw >= this.ceilLab.rzsl - this.handleHalfWidth - 10)
{
this.hideEl(this.flrLab);
clHidden = true;
this.hideEl(this.ceilLab);
}
else
{
if (this.range)
{
if (this.gap(this.flrLab, bubToAdjust) < 5)
{
this.hideEl(this.flrLab);
clHidden = false;
this.showEl(this.ceilLab);
}
else
if(this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10)
{
this.showEl(this.flrLab);
}
this.hideEl(this.ceilLab);
}
else
else if( ! clHidden)
{
this.showEl(this.flrLab);
}
this.showEl(this.ceilLab);
}
if (this.gap(this.lowLab, this.ceilLab) < 5)
// Hide or show floor label
if(this.maxLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + this.handleHalfWidth)
{
this.hideEl(this.ceilLab);
this.hideEl(this.flrLab);
}
else
else if( ! flHidden)
{
if (this.range)
this.showEl(this.flrLab);
}
},
/**
* Update slider selection bar, combined label and range label
*
* @returns {undefined}
*/
updateSelectionBar: function()
{
if (this.gap(bubToAdjust, this.ceilLab) < 5)
this.setWidth(this.selBar, this.maxH.rzsl - this.minH.rzsl);
this.setLeft(this.selBar, this.minH.rzsl + this.handleHalfWidth);
},
/**
* Update combined label position and value
*
* @returns {undefined}
*/
updateCmbLabel: function()
{
this.hideEl(this.ceilLab);
}
else
if(this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl)
{
this.showEl(this.ceilLab);
}
this.translateFn(this.scope.rzSliderModel + ' - ' + this.scope.rzSliderHigh, this.cmbLab);
this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2);
this.hideEl(this.minLab);
this.hideEl(this.maxLab);
this.showEl(this.cmbLab);
}
else
{
this.showEl(this.ceilLab);
}
}
// TODO:
if(arguments[0] === 'timeout')
{
Slider.prototype.adjustLabels = throttle(Slider.prototype.adjustLabels, 350);
this.showEl(this.maxLab);
this.showEl(this.minLab);
this.hideEl(this.cmbLab);
}
},
......@@ -536,18 +573,17 @@ function throttle(func, wait, options) {
roundStep: function(value)
{
var step = this.step,
decimals = Math.pow(10, this.precision),
remainder = (value - this.minValue) % step,
steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;
return +(steppedValue * decimals / decimals).toFixed(this.precision);
return +(steppedValue).toFixed(this.precision);
},
/**
* Hide element
*
* @param element
* @returns {jqLite} The jqLite
* @returns {jqLite} The jqLite wrapped DOM element
*/
hideEl: function (element)
{
......@@ -565,105 +601,67 @@ function throttle(func, wait, options) {
return element.css({opacity: 1});
},
/**
* Get element's offsetLeft
*
* @param element The jqLite wrapped DOM element
* @returns {number}
*/
offsetLeft: function (element)
{
return element[0].offsetLeft;
},
/**
* Get element's offsetWidth
*
* @param element The jqLite wrapped DOM element
* @returns {number}
*/
offsetWidth: function (element)
{
return element[0].offsetWidth;
},
/**
* Get element's offsetWidth / 2
*
* @param element The jqLite wrapped DOM element
* @returns {number}
*/
halfOffsetWidth: function (element)
{
return element[0].offsetWidth / 2;
},
/**
* Set element left offset
*
* @param element The jqLite wrapped DOM element
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} left
* @returns {number}
*/
setLeft: function (element, left)
setLeft: function (elem, left)
{
element.css({left: left + 'px'});
elem.rzsl = left;
elem.css({left: left + 'px'});
return left;
},
/**
* Fit element into slider bar
* Get element width
*
* @param element The jqLite wrapped DOM element
*/
fitToBar: function (element)
{
this.setLeft(element, Math.min(Math.max(0, this.offsetLeft(element)), this.barWidth - this.offsetWidth(element)));
},
/**
* Get gap in pixels between two elements accounting for elements widths
*
* @param element1 The jqLite wrapped DOM element
* @param element2 The jqLite wrapped DOM element
* @param {jqLite} elem The jqLite wrapped DOM element
* @returns {number}
*/
gap: function (element1, element2)
getWidth: function(elem)
{
return this.offsetLeft(element2) - this.offsetLeft(element1) - this.offsetWidth(element1);
var val = elem[0].getBoundingClientRect();
elem.rzsw = val.right - val.left;
return elem.rzsw;
},
/**
* Translate value to percent (between min and max value)
* Set element width
*
* @param value
* @returns {number}
* @param {jqLite} elem The jqLite wrapped DOM element
* @param {number} width
* @returns {*}
*/
percentValue: function (value)
setWidth: function(elem, width)
{
return ((value - this.minValue) / this.valueRange) * 100;
elem.rzsw = width;
elem.css({width: width + 'px'});
return width;
},
/**
* Translate left offset in pixels to percentages
* Translate value to pixel offset
*
* @param {number} offset The left offset
* @param {number} val
* @returns {number}
*/
percentOffset: function (offset)
valueToOffset: function(val)
{
return ((offset - this.minOffset) / this.offsetRange) * 100;
return (val - this.minValue) * this.maxLeft / this.valueRange;
},
/**
* Translate left offset in percentages to pixels
* Translate offset to model value
*
* @param {number} percent The value in percentages
* @returns {number} The left offset
* @param {number} offset
* @returns {number}
*/
percentToOffset: function (percent)
offsetToValue: function(offset)
{
return percent * this.offsetRange / 100;
return (offset / this.maxLeft) * this.valueRange + this.minValue;
},
// Events
......@@ -673,112 +671,125 @@ function throttle(func, wait, options) {
*
* @returns {undefined}
*/
bindToInputEvents: function()
bindEvents: function()
{
this.minPtr.on('mousedown', angular.bind(this, this.onStart, this.minPtr, this.refLow));
if(this.range) { this.maxPtr.on('mousedown', angular.bind(this, this.onStart, this.maxPtr, this.refHigh)) }
this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
if(this.range) { this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')) }
this.minPtr.on('touchstart', angular.bind(this, this.onStart, this.minPtr, this.refLow));
if(this.range) { this.maxPtr.on('touchstart', angular.bind(this, this.onStart, this.maxPtr, this.refHigh)) }
this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
if(this.range) { this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')) }
},
/**
* onStart event handler
*
* @param {Object} pointer The jqLite wrapped DOM element
* @param {string} ref One of the refLow, refHigh
* @param {string} ref One of the refLow, refHigh values
* @param {Event} event The event
* @returns {undefined}
*/
onStart: function (pointer, ref, event)
{
var which;
event.stopPropagation();
event.preventDefault();
if(this.tracking !== '') { return }
this.tracking = ref;
switch (ref)
{
case 'rzSliderModel':
case 'rzSliderLow':
which = 'low';
break;
case 'rzSliderHigh':
which = 'high';
break;
}
pointer.addClass('active');
event.stopPropagation();
event.preventDefault();
if(event.touches)
{
$document.on('touchmove', angular.bind(this, this.onMove, which));
$document.on('touchend', angular.bind(this, this.onEnd, pointer));
$document.on('touchmove', angular.bind(this, this.onMove, pointer));
$document.on('touchend', angular.bind(this, this.onEnd));
}
else
{
$document.on('mousemove', angular.bind(this, this.onMove, which));
$document.on('mouseup', angular.bind(this, this.onEnd, pointer));
$document.on('mousemove', angular.bind(this, this.onMove, pointer));
$document.on('mouseup', angular.bind(this, this.onEnd));
}
},
/**
* onMove event handler
*
* @param {string} ref
* @param {jqLite} pointer
* @param {Event} event The event
* @returns {undefined}
*/
onMove: function (ref, event)
onMove: function (pointer, event)
{
var eventX = event.clientX || event.touches[0].clientX,
newOffset, newPercent, newValue;
sliderLO = this.sliderElem.rzsl,
newOffset = eventX - sliderLO - this.handleHalfWidth,
newValue;
newOffset = eventX - this.element[0].getBoundingClientRect().left - this.ptrHalfWidth;
newOffset = Math.max(Math.min(newOffset, this.maxOffset), this.minOffset);
if(newOffset <= 0)
{
if(pointer.rzsl !== 0)
{
this.scope[this.tracking] = this.minValue;
this.updateHandles(this.tracking, 0);
this.scope.$apply();
}
newPercent = this.percentOffset(newOffset);
newValue = this.minValue + (this.valueRange * newPercent / 100.0);
return;
}
if(newOffset >= this.maxLeft)
{
if(pointer.rzsl !== this.maxLeft)
{
this.scope[this.tracking] = this.maxValue;
this.updateHandles(this.tracking, this.maxLeft);
this.scope.$apply();
}
return;
}
newValue = this.offsetToValue(newOffset);
newValue = this.roundStep(newValue);
if (this.range)
{
if (this.tracking === this.refLow && newValue >= this.scope[this.refHigh])
if (this.tracking === 'rzSliderModel' && newValue >= this.scope.rzSliderHigh)
{
this.scope[this.tracking] = this.scope[this.refHigh];
this.tracking = this.refHigh;
this.minPtr.removeClass('active');
this.maxPtr.addClass('active');
this.scope[this.tracking] = this.scope.rzSliderHigh;
this.updateHandles(this.tracking, this.maxH.rzsl);
this.tracking = 'rzSliderHigh';
this.minH.removeClass('active');
this.maxH.addClass('active');
}
else if(newValue <= this.scope[this.refLow])
else if(this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel)
{
this.scope[this.tracking] = this.scope[this.refLow];
this.tracking = this.refLow;
this.maxPtr.removeClass('active');
this.minPtr.addClass('active');
this.scope[this.tracking] = this.scope.rzSliderModel;
this.updateHandles(this.tracking, this.minH.rzsl);
this.tracking = 'rzSliderModel';
this.maxH.removeClass('active');
this.minH.addClass('active');
}
}
this.scope[this.tracking] = this.roundStep(newValue);
this.updateHandles(ref);
this.adjustLabels('onMove');
if(this.scope[this.tracking] !== newValue)
{
this.scope[this.tracking] = newValue;
this.updateHandles(this.tracking, newOffset);
this.scope.$apply();
}
},
/**
* onEnd event handler
*
* @param {Object} pointer The jqLite wrapped DOM element
* @param {Event} event The event
* @returns {undefined}
*/
onEnd: function(pointer, event)
onEnd: function(event)
{
pointer.removeClass('active');
this.minH.removeClass('active');
this.maxH.removeClass('active');
if(event.touches)
{
......@@ -808,7 +819,6 @@ function throttle(func, wait, options) {
rzSliderStep: '@',
rzSliderPrecision: '@',
rzSliderModel: '=?',
rzSliderLow: '=?',
rzSliderHigh: '=?',
rzSliderTranslate: '&'
},
......@@ -816,51 +826,35 @@ function throttle(func, wait, options) {
'<span class="bar selection"></span>' + // 1 Highlight between two handles
'<span class="pointer"></span>' + // 2 Left slider handle
'<span class="pointer"></span>' + // 3 Right slider handle
'<span class="bubble selection"></span>' + // 4 Range label
'<span class="bubble limit" ng-bind="rzSliderTranslate({value: rzSliderFloor})"></span>' + // 5 Floor label
'<span class="bubble limit" ng-bind="rzSliderTranslate({value: rzSliderCeil})" class="bubble limit"></span>' + // 6 Ceiling label
'<span class="bubble"></span>' + // 7 Label above left slider handle
'<span class="bubble"></span>' + // 8 Label above right slider handle
'<span class="bubble"></span>', // 9 Range label when the slider handles are close ex. 15 - 17
compile: function (element, attributes)
{
var
// The slider child elements
children = element.children(),
range = (attributes.rzSliderModel === undefined) && ((attributes.rzSliderLow !== undefined) && (attributes.rzSliderHigh !== undefined)),
refLow = range ? 'rzSliderLow' : 'rzSliderModel',
refHigh = 'rzSliderHigh';
// Set attribute value to function call
if (attributes.rzSliderTranslate)
{
attributes.$set('rzSliderTranslate', '' + attributes.rzSliderTranslate + '(value)');
}
// Range label
angular.element(children[4]).attr('ng-bind', 'rzSliderTranslate({value: rzSliderDiff})');
// Label above low slider
angular.element(children[7]).attr('ng-bind', 'rzSliderTranslate({value: ' + refLow + '})');
// Label above high slider
angular.element(children[8]).attr('ng-bind', 'rzSliderTranslate({value: ' + refHigh + '})');
// Combined label for low and high
angular.element(children[9]).attr('ng-bind-html', 'rzSliderTranslate({value: ' + refLow + '}) + " - " + rzSliderTranslate({value: ' + refHigh + '})');
'<span class="bubble limit"></span>' + // 4 Floor label
'<span class="bubble limit"></span>' + // 5 Ceiling label
'<span class="bubble"></span>' + // 6 Label above left slider handle
'<span class="bubble"></span>' + // 7 Label above right slider handle
'<span class="bubble"></span>', // 8 Range label when the slider handles are close ex. 15 - 17
return {
post: function(scope, elem, attr)
link: function(scope, elem, attr)
{
return new Slider(scope, elem, attr);
}
};
}
};
}]);
// IDE assist
/**
* @name ngScope
*
* @property {number} rzSliderModel
* @property {number} rzSliderHigh
* @property {number} rzSliderCeil
*/
/**
* @name jqLite
*
* @property {number} rzsl
* @property {number} rzsw
* @property {string} rzsv
*/
/**
......
......@@ -13,6 +13,13 @@
border-radius: @radius;
}
@handleActiveColor: #67b700;
@handleHoverColor: #67b700;
@labelTextColor: #67b700;
@handleBgColor: #fff;
@handleInnerColor: #71818e;
@limitLabelTextColor: @labelTextColor;
rzslider {
display: inline-block;
position: relative;
......@@ -52,7 +59,7 @@ rzslider span.pointer {
width: 32px;
height: 32px;
top: -15px;
background-color: #fff;
background-color: @handleBgColor;
z-index: 2;
.rounded(16px);
}
......@@ -65,22 +72,22 @@ rzslider span.pointer:after {
top: 12px;
left: 12px;
.rounded(4px);
background: #71818e;
background: @handleInnerColor;
}
rzslider span.pointer:hover:after {
background-color: #67b700;
background-color: @handleHoverColor;
}
rzslider span.pointer.active:after {
background-color: #67b700;
background-color: @handleActiveColor;
}
rzslider span.bubble {
cursor: default;
top: -32px;
padding: 1px 3px 1px 3px;
color: #67b700;
color: @labelTextColor;
}
rzslider span.bubble.selection {
......@@ -88,5 +95,5 @@ rzslider span.bubble.selection {
}
rzslider span.bubble.limit {
/*color: #808080;*/
color: @limitLabelTextColor;
}
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