UNPKG

8.79 kBJavaScriptView Raw
1/**
2 * spinner directive, an input control for numbers
3 *
4 * <input spinner>
5 *
6 */
7define('directives/spinner',[
8 'directives/ng-directives',
9 'jquery'
10],function(module) {
11 var Spinner = (function() {
12 var defaults = {
13 selector: '.frame',
14 next: '.next',
15 prev: '.prev',
16 snap: 5,
17 drift: 500
18 };
19
20 //clamps the given value
21 function clamp(value,min,max) {
22 return Math.max(min,Math.min(max,value));
23 }
24 //vector difference
25 function vdiff(v1,v2) {
26 return v1.map(function(s1,i) {
27 return s1-v2[i];
28 });
29 }
30
31 function Spinner(el,options) {
32 this.elContainer = $(el);
33 this.setup(options);
34 this.initialize();
35 }
36
37 function getEvent(e) {
38 if (typeof e.originalEvent.touches !== "undefined" && e.originalEvent.touches.length) {
39 return e.originalEvent.touches[0];
40 }
41 return e;
42 }
43
44 function handle(fn,scope) {
45 return function(e) {
46 fn.call(scope||this,e,getEvent(e));
47 };
48 }
49
50 function is_touch_device() {
51 try {
52 document.createEvent("TouchEvent");
53 return true;
54 } catch (e) {
55 return false;
56 }
57 }
58 var isTouch = is_touch_device();
59 var events = {
60 start: isTouch?'touchstart':'mousedown',
61 move: isTouch?'touchmove':'mousemove',
62 end: isTouch?'touchend':'mouseup'
63 };
64
65 Spinner.prototype.setup = function(options) {
66 this.options = $.extend(defaults,options);
67 this.elFrame = this.elContainer.children(this.options.selector);
68 this.elPrev = this.elContainer.children(this.options.prev);
69 this.elNext = this.elContainer.children(this.options.next);
70 this.min = 0;
71 this.max = this.elFrame.children().length-1;
72 this.step = this.elFrame.children().width();
73 };
74
75 Spinner.prototype.initialize = function() {
76 this.elPrev.on(events.start,handle(this.prev,this));
77 this.elNext.on(events.start,handle(this.next,this));
78 this.elContainer.on(events.start,handle(this.dragstart,this));
79 $(document).on(events.move,handle(this.drag,this));
80 $(document).on(events.end,handle(this.dragend,this));
81 this.set(this.defaultValue,true);
82 };
83
84 function transform(el,value) {
85 //use margin for now, since translation seems to break in chrome until the element is redrawn
86 // var translate = ['translate3d(',value,'px,0px,0px)'].join('');
87 // el.css({
88 // transform: translate,
89 // MozTransform: translate,
90 // WebkitTransform: translate
91 // });
92 el.css({
93 marginLeft: value+'px'
94 });
95 }
96
97 Spinner.prototype.set = function(value,trigger) {
98 this.max = this.elFrame.children().length-1;
99 if (value > 0) {
100 this.min = 1;
101 }
102 if (value !== undefined) {
103 this.value = clamp(value||0,this.min,this.max);
104 this.offset = -1*this.step*this.value;
105 } else {
106 this.value = undefined;
107 this.offset = 0;
108 }
109 transform(this.elFrame,this.offset);
110 if (trigger) {
111 this.elContainer.trigger('change',[this.value-1,this]);
112 }
113 };
114
115 Spinner.prototype.repaint = function() {
116 this.set(this.value, false);
117 };
118
119 Spinner.prototype.prev = function(e) {
120 this.set(this.value-1,true);
121 if (e) {
122 e.stopPropagation();
123 }
124 };
125
126 Spinner.prototype.next = function(e) {
127 this.set((this.value||0)+1,true);
128 if (e) {
129 e.stopPropagation();
130 }
131 };
132
133 Spinner.prototype.dragstart = function(e,ev) {
134 this.dragging = true;
135 this.elContainer.addClass('dragging');
136 this.x = [ev.screenX,ev.screenY];
137 this.t = e.timeStamp;
138 };
139 Spinner.prototype.drag = function(e,ev) {
140 if (this.dragging) {
141 this.dx = vdiff([ev.screenX,ev.screenY],this.x);
142 this.dt = (e.timeStamp - this.t)/1000;
143 this.x = [ev.screenX,ev.screenY];
144 this.t = e.timeStamp;
145 this.v = [this.dx[0]/this.dt,this.dx[1]/this.dt];
146 this.offset += this.dx[0];
147 transform(this.elFrame,this.offset);
148 e.stopPropagation();
149 e.preventDefault();
150 }
151 };
152 Spinner.prototype.dragend = function(e,ev) {
153 if (this.dragging) {
154 this.elContainer.removeClass('dragging');
155 if (Math.abs(this.v[0]) < this.options.snap) {
156 this.snap();
157 } else {
158 this.drift();
159 }
160 this.dragging = false;
161 }
162 };
163
164 Spinner.prototype.snap = function() {
165 var nearest = Math.round(-this.offset / this.step);
166 this.set(nearest,true);
167 };
168 Spinner.prototype.drift = function() {
169 var dist = this.v[0]*this.options.drift/1000;
170 var nearest = Math.round(-(this.offset+dist) / this.step);
171 this.elContainer.addClass('drifting');
172 this.set(nearest,true);
173 window.setTimeout($.proxy(function() {
174 this.elContainer.removeClass('drifting');
175 },this),this.options.drift);
176 };
177
178 return Spinner;
179 }());
180
181 $.fn.spinner = function(options) {
182 return this.each(function() {
183 new Spinner($(this),options);
184 });
185 };
186
187 return module.directive('fllSpinner',[
188 '$parse','$timeout',
189 function($parse,$timeout) {
190 function numbers(min,max) {
191 var i,nrs = [""];
192 for (i=min; i<=max; i++) {
193 nrs.push(i);
194 }
195 return nrs;
196 }
197
198 return {
199 scope: true,
200 template: [
201 '<span class="spinner">',
202 '<span class="frame">',
203 '<span ng-repeat="n in numbers">{{n}}</span>',
204 '</span>',
205 '<span class="inner"></span>',
206 '<button class="prev"><i class="material-icons">keyboard_arrow_left</i></button>',
207 '<button class="next"><i class="material-icons">keyboard_arrow_right</i></button>',
208 '</span>'
209 ].join(''),
210 link: function($scope,$element,$attrs) {
211 var min,max,s;
212 var model = $parse($attrs.ngModel);
213 var setting = false;
214 $scope.$watch($attrs.min,function(newValue) {
215 min = 1*newValue;
216 repaint();
217 });
218 $scope.$watch($attrs.max,function(newValue) {
219 max = 1*newValue;
220 repaint();
221 });
222 $scope.$parent.$watch($attrs.ngModel,function(newValue,oldValue) {
223 if (s && !setting) {
224 if (newValue === undefined) {
225 s.set(undefined);
226 } else {
227 s.set(newValue-min);
228 }
229 }
230 setting = false;
231 });
232
233 $timeout(init,false);
234
235 function init() {
236 var el = $element.children().first();
237 s = new Spinner(el);
238
239 el.bind('change',function(event, n, element) {
240 //TODO: work with numbers
241 model.assign($scope.$parent,String(n+min));
242 setting = true;
243 $scope.$apply();
244 });
245 }
246
247 function repaint() {
248 $scope.numbers = numbers(min,max);
249 $timeout(function() {
250 s.repaint();
251 },false);
252 }
253
254 }
255 };
256 }
257 ]);
258});