├── .gitignore ├── src ├── slider-template.jade ├── angular-slider.styl └── angular-slider.coffee ├── package.json ├── LICENSE ├── angular-slider.min.css ├── angular-slider.css ├── README.md ├── angular-slider.min.js └── angular-slider.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /src/slider-template.jade: -------------------------------------------------------------------------------- 1 | span.bar 2 | span.bar.selection 3 | span.pointer 4 | span.pointer 5 | span.bubble.selection 6 | span.bubble.limit(ng-bind-html-unsafe="translate({value: floor})") 7 | span.bubble.limit(ng-bind-html-unsafe="translate({value: ceiling})") 8 | span.bubble 9 | span.bubble 10 | span.bubble 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-slider", 3 | "version": "0.1.6", 4 | "description": "Slider directive implementation for AngularJS, without jQuery dependencies.", 5 | "main": "angular-slider.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/prajwalkman/angular-slider.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "slider", 16 | "jquery", 17 | "jquery-ui" 18 | ], 19 | "devDependencies": { 20 | "stylus": "latest", 21 | "nib": "latest", 22 | "coffee-script": "latest", 23 | "uglify-js": "latest", 24 | "jade": "latest" 25 | }, 26 | "author": "Prajwal K Manjunath", 27 | "license": "MIT", 28 | "readmeFilename": "README.md", 29 | "gitHead": "428b32578afe6e43813e398c7825a1f36fe93a19" 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Prajwal Manjunath 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/angular-slider.styl: -------------------------------------------------------------------------------- 1 | @import 'nib' 2 | 3 | slider 4 | display inline-block 5 | position relative 6 | height 7px 7 | width 100% 8 | margin 25px 5px 25px 5px 9 | vertical-align middle 10 | span 11 | white-space nowrap 12 | position absolute 13 | display inline-block 14 | &.base 15 | width 100% 16 | height 100% 17 | padding 0 18 | &.bar 19 | width 100% 20 | height 100% 21 | z-index 0 22 | border-radius 1em/1em 23 | background linear-gradient(top, lighten(gray, 50%), lighten(gray, 10%)) 24 | box-shadow inset 2px 2px 5px 25 | &.selection 26 | width 0% 27 | z-index 1 28 | border-radius none 29 | background linear-gradient(top, darken(#31BFFF, 10%), darken(#31BFFF, 20%)) 30 | box-shadow none 31 | &.pointer 32 | cursor pointer 33 | width 20px 34 | height 20px 35 | top -8px 36 | background-color white 37 | border 1px solid black 38 | z-index 2 39 | border-radius 1em/1em 40 | &:after 41 | content '' 42 | background-color gray 43 | width 8px 44 | height 8px 45 | position absolute 46 | top 6px 47 | left 6px 48 | border-radius 1em/1em 49 | &:hover:after 50 | background-color black 51 | &.active:after 52 | background-color red 53 | &.bubble 54 | cursor default 55 | top -22px 56 | padding 1px 3px 1px 3px 57 | font-size 0.7em 58 | font-family sans-serif 59 | &.selection 60 | top 15px 61 | &.limit 62 | color gray 63 | -------------------------------------------------------------------------------- /angular-slider.min.css: -------------------------------------------------------------------------------- 1 | slider{display:inline-block;position:relative;height:7px;width:100%;margin:25px 5px 25px 5px;vertical-align:middle;} 2 | slider span{white-space:nowrap;position:absolute;display:inline-block;} 3 | slider span.base{width:100%;height:100%;padding:0} 4 | slider span.bar{width:100%;height:100%;z-index:0;-webkit-border-radius:1em/1em;border-radius:1em/1em;background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #c0c0c0), color-stop(1, #8d8d8d));background:-webkit-linear-gradient(top, #c0c0c0 0, #8d8d8d 100%);background:-moz-linear-gradient(top, #c0c0c0 0, #8d8d8d 100%);background:-o-linear-gradient(top, #c0c0c0 0, #8d8d8d 100%);background:-ms-linear-gradient(top, #c0c0c0 0, #8d8d8d 100%);background:linear-gradient(top, #c0c0c0 0, #8d8d8d 100%);-webkit-box-shadow:inset 2px 2px 5px;box-shadow:inset 2px 2px 5px;} 5 | slider span.bar.selection{width:0%;z-index:1;background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #13b6ff), color-stop(1, #00a8f3));background:-webkit-linear-gradient(top, #13b6ff 0, #00a8f3 100%);background:-moz-linear-gradient(top, #13b6ff 0, #00a8f3 100%);background:-o-linear-gradient(top, #13b6ff 0, #00a8f3 100%);background:-ms-linear-gradient(top, #13b6ff 0, #00a8f3 100%);background:linear-gradient(top, #13b6ff 0, #00a8f3 100%);-webkit-box-shadow:none;box-shadow:none} 6 | slider span.pointer{cursor:pointer;width:20px;height:20px;top:-8px;background-color:#fff;border:1px solid #000;z-index:2;-webkit-border-radius:1em/1em;border-radius:1em/1em;} 7 | slider span.pointer:after{content:'';background-color:#808080;width:8px;height:8px;position:absolute;top:6px;left:6px;-webkit-border-radius:1em/1em;border-radius:1em/1em} 8 | slider span.pointer:hover:after{background-color:#000} 9 | slider span.pointer.active:after{background-color:#f00} 10 | slider span.bubble{cursor:default;top:-22px;padding:1px 3px 1px 3px;font-size:.7em;font-family:sans-serif;} 11 | slider span.bubble.selection{top:15px} 12 | slider span.bubble.limit{color:#808080} 13 | -------------------------------------------------------------------------------- /angular-slider.css: -------------------------------------------------------------------------------- 1 | slider { 2 | display: inline-block; 3 | position: relative; 4 | height: 7px; 5 | width: 100%; 6 | margin: 25px 5px 25px 5px; 7 | vertical-align: middle; 8 | } 9 | slider span { 10 | white-space: nowrap; 11 | position: absolute; 12 | display: inline-block; 13 | } 14 | slider span.base { 15 | width: 100%; 16 | height: 100%; 17 | padding: 0; 18 | } 19 | slider span.bar { 20 | width: 100%; 21 | height: 100%; 22 | z-index: 0; 23 | -webkit-border-radius: 1em/1em; 24 | border-radius: 1em/1em; 25 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #c0c0c0), color-stop(1, #8d8d8d)); 26 | background: -webkit-linear-gradient(top, #c0c0c0 0, #8d8d8d 100%); 27 | background: -moz-linear-gradient(top, #c0c0c0 0, #8d8d8d 100%); 28 | background: -o-linear-gradient(top, #c0c0c0 0, #8d8d8d 100%); 29 | background: -ms-linear-gradient(top, #c0c0c0 0, #8d8d8d 100%); 30 | background: linear-gradient(top, #c0c0c0 0, #8d8d8d 100%); 31 | -webkit-box-shadow: inset 2px 2px 5px; 32 | box-shadow: inset 2px 2px 5px; 33 | } 34 | slider span.bar.selection { 35 | width: 0%; 36 | z-index: 1; 37 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #13b6ff), color-stop(1, #00a8f3)); 38 | background: -webkit-linear-gradient(top, #13b6ff 0, #00a8f3 100%); 39 | background: -moz-linear-gradient(top, #13b6ff 0, #00a8f3 100%); 40 | background: -o-linear-gradient(top, #13b6ff 0, #00a8f3 100%); 41 | background: -ms-linear-gradient(top, #13b6ff 0, #00a8f3 100%); 42 | background: linear-gradient(top, #13b6ff 0, #00a8f3 100%); 43 | -webkit-box-shadow: none; 44 | box-shadow: none; 45 | } 46 | slider span.pointer { 47 | cursor: pointer; 48 | width: 20px; 49 | height: 20px; 50 | top: -8px; 51 | background-color: #fff; 52 | border: 1px solid #000; 53 | z-index: 2; 54 | -webkit-border-radius: 1em/1em; 55 | border-radius: 1em/1em; 56 | } 57 | slider span.pointer:after { 58 | content: ''; 59 | background-color: #808080; 60 | width: 8px; 61 | height: 8px; 62 | position: absolute; 63 | top: 6px; 64 | left: 6px; 65 | -webkit-border-radius: 1em/1em; 66 | border-radius: 1em/1em; 67 | } 68 | slider span.pointer:hover:after { 69 | background-color: #000; 70 | } 71 | slider span.pointer.active:after { 72 | background-color: #f00; 73 | } 74 | slider span.bubble { 75 | cursor: default; 76 | top: -22px; 77 | padding: 1px 3px 1px 3px; 78 | font-size: 0.7em; 79 | font-family: sans-serif; 80 | } 81 | slider span.bubble.selection { 82 | top: 15px; 83 | } 84 | slider span.bubble.limit { 85 | color: #808080; 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEPRECATED 2 | ========== 3 | 4 | I am unable to maintain or update this repository any more. If you know more well maintained forks, let me know so I can link it here. 5 | 6 | PopSugar: https://github.com/PopSugar/angular-slider (updated to work in current Angular version) 7 | 8 | Venturocket: http://github.com/venturocket/angular-slider (highly modified) 9 | 10 | RzSlider: https://github.com/rzajac/angularjs-slider (rewritten in JavaScript) 11 | 12 | 13 | angular-slider 14 | ============== 15 | 16 | Slider directive implementation for AngularJS, without jQuery dependencies. Requires AngularJS v1.1.4 or higher (optional isolate scope bindings support). 17 | 18 | ### Example: 19 | 20 | 27 | 28 | ### Range: 29 | 30 | 38 | 39 | ### Formatting: 40 | 41 | Raw data can be formatted as text using the _translate_ attribute. 42 | In your controller: 43 | 44 | $scope.currencyFormatting = function(value) { return value.toString() + " $" } 45 | 46 | And your HTML: 47 | 48 | 49 | 50 | ### Usage: 51 | 52 | Make sure to load AngularJS first, and then `angular-slider.js`. Also include the related `angular-slider.css`. 53 | 54 | The module is named `uiSlider`. To enable it, you must simply list it as a dependency in your app. Example: 55 | 56 | var app = angular.module('app', ['uiSlider', 'ngResource', ...]); 57 | 58 | You can then use it in your templates like so: 59 | 60 | 61 | ... 62 | 63 | ... 64 | 65 | 66 | 67 | 68 | 69 | ### Known issues: 70 | 71 | 1. When applying filters or orders within an ng-repeat directive, the element can abruptly change its position when the value attached to the slider causes a filter to activate or the order to change. 72 | Example: In the above snippet, it would be a very bad idea to order the list by item.cost. 73 | 74 | ### Roadmap: 75 | 76 | 1. ~~Touch events support~~ 77 | 2. Smooth curve heterogeneity 78 | 3. Filters support 79 | 80 | ### License: MIT 81 | -------------------------------------------------------------------------------- /angular-slider.min.js: -------------------------------------------------------------------------------- 1 | (function(){var n,t,e,a,r,o,s,l,u,i,c,p,f,d,v,h,g;n="uiSlider",t="slider",e=function(n){return angular.element(n)},p=function(n){return""+n+"px"},s=function(n){return n.css({opacity:0})},v=function(n){return n.css({opacity:1})},i=function(n,t){return n.css({left:t})},o=function(n){return n[0].offsetWidth/2},c=function(n){return n[0].offsetLeft},g=function(n){return n[0].offsetWidth},r=function(n,t){return c(t)-c(n)-g(n)},a=function(n,t){return n.attr("ng-bind-html-unsafe",t)},d=function(n,t,e,a){var r,o,s,l;return null==a&&(a=0),null==e&&(e=1/Math.pow(10,t)),o=(n-a)%e,l=o>e/2?n+e-o:n-o,r=Math.pow(10,t),s=l*r/r,s.toFixed(t)},l={mouse:{start:"mousedown",move:"mousemove",end:"mouseup"},touch:{start:"touchstart",move:"touchmove",end:"touchend"}},h=function(n){return{restrict:"EA",scope:{floor:"@",ceiling:"@",step:"@",precision:"@",ngModel:"=?",ngModelLow:"=?",ngModelHigh:"=?",translate:"&"},template:'',compile:function(t,u){var f,h,m,b,M,w,F,C,L,x,$,y,H,I,E,R,W,X,z;if(u.translate&&u.$set("translate",""+u.translate+"(value)"),x=null==u.ngModel&&null!=u.ngModelLow&&null!=u.ngModelHigh,X=function(){var n,a,r,o;for(r=t.children(),o=[],n=0,a=r.length;a>n;n++)m=r[n],o.push(e(m));return o}(),M=X[0],H=X[1],L=X[2],C=X[3],I=X[4],b=X[5],f=X[6],F=X[7],w=X[8],h=X[9],y=x?"ngModelLow":"ngModel",$="ngModelHigh",a(I,"'Range: ' + translate({value: diff})"),a(F,"translate({value: "+y+"})"),a(w,"translate({value: "+$+"})"),a(h,"translate({value: "+y+"}) + ' - ' + translate({value: "+$+"})"),!x)for(z=[H,C,I,w,h],R=0,W=z.length;W>R;R++)t=z[R],t.remove();return E=[y,"floor","ceiling"],x&&E.push($),{post:function(t,a,u){var m,R,W,X,z,A,B,D,P,S,j,k,q,G,J;for(R=!1,D=e(document),u.translate||(t.translate=function(n){return n.value}),S=m=A=X=B=z=k=P=void 0,W=function(){var n,e,a,r,s;for(null==(r=t.precision)&&(t.precision=0),null==(s=t.step)&&(t.step=1),e=0,a=E.length;a>e;e++)n=E[e],t[n]=d(parseFloat(t[n]),parseInt(t.precision),parseFloat(t.step),parseFloat(t.floor));return t.diff=d(t[$]-t[y],parseInt(t.precision),parseFloat(t.step),parseFloat(t.floor)),S=o(L),m=g(M),A=0,X=m-g(L),B=parseFloat(u.floor),z=parseFloat(u.ceiling),k=z-B,P=X-A},j=function(){var n,e,u,M,E,z,j,q;return W(),M=function(n){return 100*((n-A)/P)},z=function(n){return 100*((n-B)/k)},E=function(n){return p(n*P/100)},u=function(n){return i(n,p(Math.min(Math.max(0,c(n)),m-g(n))))},q=function(){var n,e;return i(f,p(m-g(f))),e=z(t[y]),i(L,E(e)),i(F,p(c(L)-o(F)+S)),x?(n=z(t[$]),i(C,E(n)),i(w,p(c(C)-o(w)+S)),i(H,p(c(L)+S)),H.css({width:E(n-e)}),i(I,p(c(H)+o(H)-o(I))),i(h,p(c(H)+o(H)-o(h)))):void 0},n=function(){var n;return u(F),n=w,x&&(u(w),u(I),10>r(F,w)?(s(F),s(w),u(h),v(h),n=h):(v(F),v(w),s(h),n=w)),5>r(b,F)?s(b):x?5>r(b,n)?s(b):v(b):v(b),5>r(F,f)?s(f):x?5>r(n,f)?s(f):v(f):v(f)},e=function(n,e,r){var o,s,l;return o=function(){return n.removeClass("active"),D.unbind(r.move),D.unbind(r.end)},s=function(n){var r,o,s,l;return r=n.clientX||n.touches[0].clientX,o=r-a[0].getBoundingClientRect().left-S,o=Math.max(Math.min(o,X),A),s=M(o),l=B+k*s/100,x&&(e===y?l>t[$]&&(e=$,L.removeClass("active"),C.addClass("active")):t[y]>l&&(e=y,C.removeClass("active"),L.addClass("active"))),l=d(l,parseInt(t.precision),parseFloat(t.step),parseFloat(t.floor)),t[e]=l,t.$apply()},l=function(t){return n.addClass("active"),W(),t.stopPropagation(),t.preventDefault(),D.bind(r.move,s),D.bind(r.end,o)},n.bind(r.start,l)},j=function(){var n,t,a,r,o,s;for(R=!0,n=function(n){return e(L,y,l[n]),e(C,$,l[n])},o=["touch","mouse"],s=[],a=0,r=o.length;r>a;a++)t=o[a],s.push(n(t));return s},q(),n(),R?void 0:j()},n(j),G=0,J=E.length;J>G;G++)q=E[G],t.$watch(q,j);return window.addEventListener("resize",j)}}}}},f=["$timeout",h],u=function(e,a){return a.module(n,[]).directive(t,f)},u(window,window.angular)}).call(this); -------------------------------------------------------------------------------- /src/angular-slider.coffee: -------------------------------------------------------------------------------- 1 | # CONSTANTS 2 | 3 | MODULE_NAME = 'uiSlider' 4 | SLIDER_TAG = 'slider' 5 | 6 | # HELPER FUNCTIONS 7 | 8 | angularize = (element) -> angular.element element 9 | pixelize = (position) -> "#{position}px" 10 | hide = (element) -> element.css opacity: 0 11 | show = (element) -> element.css opacity: 1 12 | offset = (element, position) -> element.css left: position 13 | halfWidth = (element) -> element[0].offsetWidth / 2 14 | offsetLeft = (element) -> element[0].offsetLeft 15 | width = (element) -> element[0].offsetWidth 16 | gap = (element1, element2) -> offsetLeft(element2) - offsetLeft(element1) - width(element1) 17 | bindHtml = (element, html) -> element.attr 'ng-bind-html-unsafe', html 18 | roundStep = (value, precision, step, floor = 0) -> 19 | step ?= 1 / Math.pow(10, precision) 20 | remainder = (value - floor) % step 21 | steppedValue = 22 | if remainder > (step / 2) 23 | then value + step - remainder 24 | else value - remainder 25 | decimals = Math.pow 10, precision 26 | roundedValue = steppedValue * decimals / decimals 27 | roundedValue.toFixed precision 28 | inputEvents = 29 | mouse: 30 | start: 'mousedown' 31 | move: 'mousemove' 32 | end: 'mouseup' 33 | touch: 34 | start: 'touchstart' 35 | move: 'touchmove' 36 | end: 'touchend' 37 | 38 | # DIRECTIVE DEFINITION 39 | 40 | sliderDirective = ($timeout) -> 41 | restrict: 'EA' 42 | scope: 43 | floor: '@' 44 | ceiling: '@' 45 | step: '@' 46 | precision: '@' 47 | ngModel: '=?' 48 | ngModelLow: '=?' 49 | ngModelHigh: '=?' 50 | translate: '&' 51 | template: '' 52 | compile: (element, attributes) -> 53 | 54 | # Expand the translation function abbreviation 55 | attributes.$set 'translate', "#{attributes.translate}(value)" if attributes.translate 56 | 57 | # Check if it is a range slider 58 | range = !attributes.ngModel? and (attributes.ngModelLow? and attributes.ngModelHigh?) 59 | 60 | # Get references to template elements 61 | [fullBar, selBar, minPtr, maxPtr, selBub, 62 | flrBub, ceilBub, lowBub, highBub, cmbBub] = (angularize(e) for e in element.children()) 63 | 64 | # Shorthand references to the 2 model scopes 65 | refLow = if range then 'ngModelLow' else 'ngModel' 66 | refHigh = 'ngModelHigh' 67 | 68 | bindHtml selBub, "'Range: ' + translate({value: diff})" 69 | bindHtml lowBub, "translate({value: #{refLow}})" 70 | bindHtml highBub, "translate({value: #{refHigh}})" 71 | bindHtml cmbBub, "translate({value: #{refLow}}) + ' - ' + translate({value: #{refHigh}})" 72 | 73 | # Remove range specific elements if not a range slider 74 | unless range 75 | element.remove() for element in [selBar, maxPtr, selBub, highBub, cmbBub] 76 | 77 | # Scope values to watch for changes 78 | watchables = [refLow, 'floor', 'ceiling'] 79 | watchables.push refHigh if range 80 | 81 | post: (scope, element, attributes) -> 82 | 83 | boundToInputs = false 84 | ngDocument = angularize document 85 | unless attributes.translate 86 | scope.translate = (value) -> value.value 87 | 88 | pointerHalfWidth = barWidth = minOffset = maxOffset = minValue = maxValue = valueRange = offsetRange = undefined 89 | 90 | dimensions = -> 91 | # roundStep the initial score values 92 | scope.precision ?= 0 93 | scope.step ?= 1 94 | scope[value] = roundStep(parseFloat(scope[value]), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)) for value in watchables 95 | scope.diff = roundStep(scope[refHigh] - scope[refLow], parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)) 96 | 97 | # Commonly used measurements 98 | pointerHalfWidth = halfWidth minPtr 99 | barWidth = width fullBar 100 | 101 | minOffset = 0 102 | maxOffset = barWidth - width(minPtr) 103 | 104 | minValue = parseFloat attributes.floor 105 | maxValue = parseFloat attributes.ceiling 106 | 107 | valueRange = maxValue - minValue 108 | offsetRange = maxOffset - minOffset 109 | 110 | updateDOM = -> 111 | dimensions() 112 | 113 | # Translation functions 114 | percentOffset = (offset) -> ((offset - minOffset) / offsetRange) * 100 115 | percentValue = (value) -> ((value - minValue) / valueRange) * 100 116 | percentToOffset = (percent) -> pixelize percent * offsetRange / 100 117 | 118 | # Fit bubble to bar width 119 | fitToBar = (element) -> offset element, pixelize(Math.min (Math.max 0, offsetLeft(element)), (barWidth - width(element))) 120 | 121 | setPointers = -> 122 | offset ceilBub, pixelize(barWidth - width(ceilBub)) 123 | newLowValue = percentValue scope[refLow] 124 | offset minPtr, percentToOffset newLowValue 125 | offset lowBub, pixelize(offsetLeft(minPtr) - (halfWidth lowBub) + pointerHalfWidth) 126 | if range 127 | newHighValue = percentValue scope[refHigh] 128 | offset maxPtr, percentToOffset newHighValue 129 | offset highBub, pixelize(offsetLeft(maxPtr) - (halfWidth highBub) + pointerHalfWidth) 130 | offset selBar, pixelize(offsetLeft(minPtr) + pointerHalfWidth) 131 | selBar.css width: percentToOffset newHighValue - newLowValue 132 | offset selBub, pixelize(offsetLeft(selBar) + halfWidth(selBar) - halfWidth(selBub)) 133 | offset cmbBub, pixelize(offsetLeft(selBar) + halfWidth(selBar) - halfWidth(cmbBub)) 134 | 135 | adjustBubbles = -> 136 | fitToBar lowBub 137 | bubToAdjust = highBub 138 | 139 | if range 140 | fitToBar highBub 141 | fitToBar selBub 142 | 143 | if gap(lowBub, highBub) < 10 144 | hide lowBub 145 | hide highBub 146 | fitToBar cmbBub 147 | show cmbBub 148 | bubToAdjust = cmbBub 149 | else 150 | show lowBub 151 | show highBub 152 | hide cmbBub 153 | bubToAdjust = highBub 154 | 155 | if gap(flrBub, lowBub) < 5 156 | hide flrBub 157 | else 158 | if range 159 | if gap(flrBub, bubToAdjust) < 5 then hide flrBub else show flrBub 160 | else 161 | show flrBub 162 | if gap(lowBub, ceilBub) < 5 163 | hide ceilBub 164 | else 165 | if range 166 | if gap(bubToAdjust, ceilBub) < 5 then hide ceilBub else show ceilBub 167 | else 168 | show ceilBub 169 | 170 | 171 | bindToInputEvents = (pointer, ref, events) -> 172 | onEnd = -> 173 | pointer.removeClass 'active' 174 | ngDocument.unbind events.move 175 | ngDocument.unbind events.end 176 | onMove = (event) -> 177 | eventX = event.clientX || event.touches[0].clientX 178 | newOffset = eventX - element[0].getBoundingClientRect().left - pointerHalfWidth 179 | newOffset = Math.max(Math.min(newOffset, maxOffset), minOffset) 180 | newPercent = percentOffset newOffset 181 | newValue = minValue + (valueRange * newPercent / 100.0) 182 | if range 183 | if ref is refLow 184 | if newValue > scope[refHigh] 185 | ref = refHigh 186 | minPtr.removeClass 'active' 187 | maxPtr.addClass 'active' 188 | else 189 | if newValue < scope[refLow] 190 | ref = refLow 191 | maxPtr.removeClass 'active' 192 | minPtr.addClass 'active' 193 | newValue = roundStep(newValue, parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)) 194 | scope[ref] = newValue 195 | scope.$apply() 196 | onStart = (event) -> 197 | pointer.addClass 'active' 198 | dimensions() 199 | event.stopPropagation() 200 | event.preventDefault() 201 | ngDocument.bind events.move, onMove 202 | ngDocument.bind events.end, onEnd 203 | pointer.bind events.start, onStart 204 | 205 | setBindings = -> 206 | boundToInputs = true 207 | bind = (method) -> 208 | bindToInputEvents minPtr, refLow, inputEvents[method] 209 | bindToInputEvents maxPtr, refHigh, inputEvents[method] 210 | bind(inputMethod) for inputMethod in ['touch', 'mouse'] 211 | 212 | setPointers() 213 | adjustBubbles() 214 | setBindings() unless boundToInputs 215 | 216 | $timeout updateDOM 217 | scope.$watch w, updateDOM for w in watchables 218 | window.addEventListener "resize", updateDOM 219 | 220 | qualifiedDirectiveDefinition = [ 221 | '$timeout' 222 | sliderDirective 223 | ] 224 | 225 | module = (window, angular) -> 226 | angular 227 | .module(MODULE_NAME, []) 228 | .directive(SLIDER_TAG, qualifiedDirectiveDefinition) 229 | 230 | module window, window.angular -------------------------------------------------------------------------------- /angular-slider.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | (function() { 3 | var MODULE_NAME, SLIDER_TAG, angularize, bindHtml, gap, halfWidth, hide, inputEvents, module, offset, offsetLeft, pixelize, qualifiedDirectiveDefinition, roundStep, show, sliderDirective, width; 4 | 5 | MODULE_NAME = 'uiSlider'; 6 | 7 | SLIDER_TAG = 'slider'; 8 | 9 | angularize = function(element) { 10 | return angular.element(element); 11 | }; 12 | 13 | pixelize = function(position) { 14 | return "" + position + "px"; 15 | }; 16 | 17 | hide = function(element) { 18 | return element.css({ 19 | opacity: 0 20 | }); 21 | }; 22 | 23 | show = function(element) { 24 | return element.css({ 25 | opacity: 1 26 | }); 27 | }; 28 | 29 | offset = function(element, position) { 30 | return element.css({ 31 | left: position 32 | }); 33 | }; 34 | 35 | halfWidth = function(element) { 36 | return element[0].offsetWidth / 2; 37 | }; 38 | 39 | offsetLeft = function(element) { 40 | return element[0].offsetLeft; 41 | }; 42 | 43 | width = function(element) { 44 | return element[0].offsetWidth; 45 | }; 46 | 47 | gap = function(element1, element2) { 48 | return offsetLeft(element2) - offsetLeft(element1) - width(element1); 49 | }; 50 | 51 | bindHtml = function(element, html) { 52 | return element.attr('ng-bind-html-unsafe', html); 53 | }; 54 | 55 | roundStep = function(value, precision, step, floor) { 56 | var decimals, remainder, roundedValue, steppedValue; 57 | 58 | if (floor == null) { 59 | floor = 0; 60 | } 61 | if (step == null) { 62 | step = 1 / Math.pow(10, precision); 63 | } 64 | remainder = (value - floor) % step; 65 | steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder; 66 | decimals = Math.pow(10, precision); 67 | roundedValue = steppedValue * decimals / decimals; 68 | return roundedValue.toFixed(precision); 69 | }; 70 | 71 | inputEvents = { 72 | mouse: { 73 | start: 'mousedown', 74 | move: 'mousemove', 75 | end: 'mouseup' 76 | }, 77 | touch: { 78 | start: 'touchstart', 79 | move: 'touchmove', 80 | end: 'touchend' 81 | } 82 | }; 83 | 84 | sliderDirective = function($timeout) { 85 | return { 86 | restrict: 'EA', 87 | scope: { 88 | floor: '@', 89 | ceiling: '@', 90 | step: '@', 91 | precision: '@', 92 | ngModel: '=?', 93 | ngModelLow: '=?', 94 | ngModelHigh: '=?', 95 | translate: '&' 96 | }, 97 | template: '', 98 | compile: function(element, attributes) { 99 | var ceilBub, cmbBub, e, flrBub, fullBar, highBub, lowBub, maxPtr, minPtr, range, refHigh, refLow, selBar, selBub, watchables, _i, _len, _ref, _ref1; 100 | 101 | if (attributes.translate) { 102 | attributes.$set('translate', "" + attributes.translate + "(value)"); 103 | } 104 | range = (attributes.ngModel == null) && ((attributes.ngModelLow != null) && (attributes.ngModelHigh != null)); 105 | _ref = (function() { 106 | var _i, _len, _ref, _results; 107 | 108 | _ref = element.children(); 109 | _results = []; 110 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 111 | e = _ref[_i]; 112 | _results.push(angularize(e)); 113 | } 114 | return _results; 115 | })(), fullBar = _ref[0], selBar = _ref[1], minPtr = _ref[2], maxPtr = _ref[3], selBub = _ref[4], flrBub = _ref[5], ceilBub = _ref[6], lowBub = _ref[7], highBub = _ref[8], cmbBub = _ref[9]; 116 | refLow = range ? 'ngModelLow' : 'ngModel'; 117 | refHigh = 'ngModelHigh'; 118 | bindHtml(selBub, "'Range: ' + translate({value: diff})"); 119 | bindHtml(lowBub, "translate({value: " + refLow + "})"); 120 | bindHtml(highBub, "translate({value: " + refHigh + "})"); 121 | bindHtml(cmbBub, "translate({value: " + refLow + "}) + ' - ' + translate({value: " + refHigh + "})"); 122 | if (!range) { 123 | _ref1 = [selBar, maxPtr, selBub, highBub, cmbBub]; 124 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 125 | element = _ref1[_i]; 126 | element.remove(); 127 | } 128 | } 129 | watchables = [refLow, 'floor', 'ceiling']; 130 | if (range) { 131 | watchables.push(refHigh); 132 | } 133 | return { 134 | post: function(scope, element, attributes) { 135 | var barWidth, boundToInputs, dimensions, maxOffset, maxValue, minOffset, minValue, ngDocument, offsetRange, pointerHalfWidth, updateDOM, valueRange, w, _j, _len1; 136 | 137 | boundToInputs = false; 138 | ngDocument = angularize(document); 139 | if (!attributes.translate) { 140 | scope.translate = function(value) { 141 | return value.value; 142 | }; 143 | } 144 | pointerHalfWidth = barWidth = minOffset = maxOffset = minValue = maxValue = valueRange = offsetRange = void 0; 145 | dimensions = function() { 146 | var value, _j, _len1, _ref2, _ref3; 147 | 148 | if ((_ref2 = scope.precision) == null) { 149 | scope.precision = 0; 150 | } 151 | if ((_ref3 = scope.step) == null) { 152 | scope.step = 1; 153 | } 154 | for (_j = 0, _len1 = watchables.length; _j < _len1; _j++) { 155 | value = watchables[_j]; 156 | scope[value] = roundStep(parseFloat(scope[value]), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)); 157 | } 158 | scope.diff = roundStep(scope[refHigh] - scope[refLow], parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)); 159 | pointerHalfWidth = halfWidth(minPtr); 160 | barWidth = width(fullBar); 161 | minOffset = 0; 162 | maxOffset = barWidth - width(minPtr); 163 | minValue = parseFloat(attributes.floor); 164 | maxValue = parseFloat(attributes.ceiling); 165 | valueRange = maxValue - minValue; 166 | return offsetRange = maxOffset - minOffset; 167 | }; 168 | updateDOM = function() { 169 | var adjustBubbles, bindToInputEvents, fitToBar, percentOffset, percentToOffset, percentValue, setBindings, setPointers; 170 | 171 | dimensions(); 172 | percentOffset = function(offset) { 173 | return ((offset - minOffset) / offsetRange) * 100; 174 | }; 175 | percentValue = function(value) { 176 | return ((value - minValue) / valueRange) * 100; 177 | }; 178 | percentToOffset = function(percent) { 179 | return pixelize(percent * offsetRange / 100); 180 | }; 181 | fitToBar = function(element) { 182 | return offset(element, pixelize(Math.min(Math.max(0, offsetLeft(element)), barWidth - width(element)))); 183 | }; 184 | setPointers = function() { 185 | var newHighValue, newLowValue; 186 | 187 | offset(ceilBub, pixelize(barWidth - width(ceilBub))); 188 | newLowValue = percentValue(scope[refLow]); 189 | offset(minPtr, percentToOffset(newLowValue)); 190 | offset(lowBub, pixelize(offsetLeft(minPtr) - (halfWidth(lowBub)) + pointerHalfWidth)); 191 | if (range) { 192 | newHighValue = percentValue(scope[refHigh]); 193 | offset(maxPtr, percentToOffset(newHighValue)); 194 | offset(highBub, pixelize(offsetLeft(maxPtr) - (halfWidth(highBub)) + pointerHalfWidth)); 195 | offset(selBar, pixelize(offsetLeft(minPtr) + pointerHalfWidth)); 196 | selBar.css({ 197 | width: percentToOffset(newHighValue - newLowValue) 198 | }); 199 | offset(selBub, pixelize(offsetLeft(selBar) + halfWidth(selBar) - halfWidth(selBub))); 200 | return offset(cmbBub, pixelize(offsetLeft(selBar) + halfWidth(selBar) - halfWidth(cmbBub))); 201 | } 202 | }; 203 | adjustBubbles = function() { 204 | var bubToAdjust; 205 | 206 | fitToBar(lowBub); 207 | bubToAdjust = highBub; 208 | if (range) { 209 | fitToBar(highBub); 210 | fitToBar(selBub); 211 | if (gap(lowBub, highBub) < 10) { 212 | hide(lowBub); 213 | hide(highBub); 214 | fitToBar(cmbBub); 215 | show(cmbBub); 216 | bubToAdjust = cmbBub; 217 | } else { 218 | show(lowBub); 219 | show(highBub); 220 | hide(cmbBub); 221 | bubToAdjust = highBub; 222 | } 223 | } 224 | if (gap(flrBub, lowBub) < 5) { 225 | hide(flrBub); 226 | } else { 227 | if (range) { 228 | if (gap(flrBub, bubToAdjust) < 5) { 229 | hide(flrBub); 230 | } else { 231 | show(flrBub); 232 | } 233 | } else { 234 | show(flrBub); 235 | } 236 | } 237 | if (gap(lowBub, ceilBub) < 5) { 238 | return hide(ceilBub); 239 | } else { 240 | if (range) { 241 | if (gap(bubToAdjust, ceilBub) < 5) { 242 | return hide(ceilBub); 243 | } else { 244 | return show(ceilBub); 245 | } 246 | } else { 247 | return show(ceilBub); 248 | } 249 | } 250 | }; 251 | bindToInputEvents = function(pointer, ref, events) { 252 | var onEnd, onMove, onStart; 253 | 254 | onEnd = function() { 255 | pointer.removeClass('active'); 256 | ngDocument.unbind(events.move); 257 | return ngDocument.unbind(events.end); 258 | }; 259 | onMove = function(event) { 260 | var eventX, newOffset, newPercent, newValue; 261 | 262 | eventX = event.clientX || event.touches[0].clientX; 263 | newOffset = eventX - element[0].getBoundingClientRect().left - pointerHalfWidth; 264 | newOffset = Math.max(Math.min(newOffset, maxOffset), minOffset); 265 | newPercent = percentOffset(newOffset); 266 | newValue = minValue + (valueRange * newPercent / 100.0); 267 | if (range) { 268 | if (ref === refLow) { 269 | if (newValue > scope[refHigh]) { 270 | ref = refHigh; 271 | minPtr.removeClass('active'); 272 | maxPtr.addClass('active'); 273 | } 274 | } else { 275 | if (newValue < scope[refLow]) { 276 | ref = refLow; 277 | maxPtr.removeClass('active'); 278 | minPtr.addClass('active'); 279 | } 280 | } 281 | } 282 | newValue = roundStep(newValue, parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)); 283 | scope[ref] = newValue; 284 | return scope.$apply(); 285 | }; 286 | onStart = function(event) { 287 | pointer.addClass('active'); 288 | dimensions(); 289 | event.stopPropagation(); 290 | event.preventDefault(); 291 | ngDocument.bind(events.move, onMove); 292 | return ngDocument.bind(events.end, onEnd); 293 | }; 294 | return pointer.bind(events.start, onStart); 295 | }; 296 | setBindings = function() { 297 | var bind, inputMethod, _j, _len1, _ref2, _results; 298 | 299 | boundToInputs = true; 300 | bind = function(method) { 301 | bindToInputEvents(minPtr, refLow, inputEvents[method]); 302 | return bindToInputEvents(maxPtr, refHigh, inputEvents[method]); 303 | }; 304 | _ref2 = ['touch', 'mouse']; 305 | _results = []; 306 | for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { 307 | inputMethod = _ref2[_j]; 308 | _results.push(bind(inputMethod)); 309 | } 310 | return _results; 311 | }; 312 | setPointers(); 313 | adjustBubbles(); 314 | if (!boundToInputs) { 315 | return setBindings(); 316 | } 317 | }; 318 | $timeout(updateDOM); 319 | for (_j = 0, _len1 = watchables.length; _j < _len1; _j++) { 320 | w = watchables[_j]; 321 | scope.$watch(w, updateDOM); 322 | } 323 | return window.addEventListener("resize", updateDOM); 324 | } 325 | }; 326 | } 327 | }; 328 | }; 329 | 330 | qualifiedDirectiveDefinition = ['$timeout', sliderDirective]; 331 | 332 | module = function(window, angular) { 333 | return angular.module(MODULE_NAME, []).directive(SLIDER_TAG, qualifiedDirectiveDefinition); 334 | }; 335 | 336 | module(window, window.angular); 337 | 338 | }).call(this); 339 | --------------------------------------------------------------------------------