angular-gem 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,563 @@
1
+ /**
2
+ * @license AngularJS v1.2.3
3
+ * (c) 2010-2014 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ (function(window, angular, undefined) {'use strict';
7
+
8
+ /**
9
+ * @ngdoc overview
10
+ * @name ngTouch
11
+ * @description
12
+ *
13
+ * # ngTouch
14
+ *
15
+ * The `ngTouch` module provides touch events and other helpers for touch-enabled devices.
16
+ * The implementation is based on jQuery Mobile touch event handling
17
+ * ([jquerymobile.com](http://jquerymobile.com/)).
18
+ *
19
+ * {@installModule touch}
20
+ *
21
+ * See {@link ngTouch.$swipe `$swipe`} for usage.
22
+ *
23
+ * <div doc-module-components="ngTouch"></div>
24
+ *
25
+ */
26
+
27
+ // define ngTouch module
28
+ /* global -ngTouch */
29
+ var ngTouch = angular.module('ngTouch', []);
30
+
31
+ /* global ngTouch: false */
32
+
33
+ /**
34
+ * @ngdoc object
35
+ * @name ngTouch.$swipe
36
+ *
37
+ * @description
38
+ * The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe
39
+ * behavior, to make implementing swipe-related directives more convenient.
40
+ *
41
+ * Requires the {@link ngTouch `ngTouch`} module to be installed.
42
+ *
43
+ * `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by
44
+ * `ngCarousel` in a separate component.
45
+ *
46
+ * # Usage
47
+ * The `$swipe` service is an object with a single method: `bind`. `bind` takes an element
48
+ * which is to be watched for swipes, and an object with four handler functions. See the
49
+ * documentation for `bind` below.
50
+ */
51
+
52
+ ngTouch.factory('$swipe', [function() {
53
+ // The total distance in any direction before we make the call on swipe vs. scroll.
54
+ var MOVE_BUFFER_RADIUS = 10;
55
+
56
+ function getCoordinates(event) {
57
+ var touches = event.touches && event.touches.length ? event.touches : [event];
58
+ var e = (event.changedTouches && event.changedTouches[0]) ||
59
+ (event.originalEvent && event.originalEvent.changedTouches &&
60
+ event.originalEvent.changedTouches[0]) ||
61
+ touches[0].originalEvent || touches[0];
62
+
63
+ return {
64
+ x: e.clientX,
65
+ y: e.clientY
66
+ };
67
+ }
68
+
69
+ return {
70
+ /**
71
+ * @ngdoc method
72
+ * @name ngTouch.$swipe#bind
73
+ * @methodOf ngTouch.$swipe
74
+ *
75
+ * @description
76
+ * The main method of `$swipe`. It takes an element to be watched for swipe motions, and an
77
+ * object containing event handlers.
78
+ *
79
+ * The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
80
+ * receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`.
81
+ *
82
+ * `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is
83
+ * watching for `touchmove` or `mousemove` events. These events are ignored until the total
84
+ * distance moved in either dimension exceeds a small threshold.
85
+ *
86
+ * Once this threshold is exceeded, either the horizontal or vertical delta is greater.
87
+ * - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow.
88
+ * - If the vertical distance is greater, this is a scroll, and we let the browser take over.
89
+ * A `cancel` event is sent.
90
+ *
91
+ * `move` is called on `mousemove` and `touchmove` after the above logic has determined that
92
+ * a swipe is in progress.
93
+ *
94
+ * `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`.
95
+ *
96
+ * `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling
97
+ * as described above.
98
+ *
99
+ */
100
+ bind: function(element, eventHandlers) {
101
+ // Absolute total movement, used to control swipe vs. scroll.
102
+ var totalX, totalY;
103
+ // Coordinates of the start position.
104
+ var startCoords;
105
+ // Last event's position.
106
+ var lastPos;
107
+ // Whether a swipe is active.
108
+ var active = false;
109
+
110
+ element.on('touchstart mousedown', function(event) {
111
+ startCoords = getCoordinates(event);
112
+ active = true;
113
+ totalX = 0;
114
+ totalY = 0;
115
+ lastPos = startCoords;
116
+ eventHandlers['start'] && eventHandlers['start'](startCoords, event);
117
+ });
118
+
119
+ element.on('touchcancel', function(event) {
120
+ active = false;
121
+ eventHandlers['cancel'] && eventHandlers['cancel'](event);
122
+ });
123
+
124
+ element.on('touchmove mousemove', function(event) {
125
+ if (!active) return;
126
+
127
+ // Android will send a touchcancel if it thinks we're starting to scroll.
128
+ // So when the total distance (+ or - or both) exceeds 10px in either direction,
129
+ // we either:
130
+ // - On totalX > totalY, we send preventDefault() and treat this as a swipe.
131
+ // - On totalY > totalX, we let the browser handle it as a scroll.
132
+
133
+ if (!startCoords) return;
134
+ var coords = getCoordinates(event);
135
+
136
+ totalX += Math.abs(coords.x - lastPos.x);
137
+ totalY += Math.abs(coords.y - lastPos.y);
138
+
139
+ lastPos = coords;
140
+
141
+ if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) {
142
+ return;
143
+ }
144
+
145
+ // One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
146
+ if (totalY > totalX) {
147
+ // Allow native scrolling to take over.
148
+ active = false;
149
+ eventHandlers['cancel'] && eventHandlers['cancel'](event);
150
+ return;
151
+ } else {
152
+ // Prevent the browser from scrolling.
153
+ event.preventDefault();
154
+ eventHandlers['move'] && eventHandlers['move'](coords, event);
155
+ }
156
+ });
157
+
158
+ element.on('touchend mouseup', function(event) {
159
+ if (!active) return;
160
+ active = false;
161
+ eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event);
162
+ });
163
+ }
164
+ };
165
+ }]);
166
+
167
+ /* global ngTouch: false */
168
+
169
+ /**
170
+ * @ngdoc directive
171
+ * @name ngTouch.directive:ngClick
172
+ *
173
+ * @description
174
+ * A more powerful replacement for the default ngClick designed to be used on touchscreen
175
+ * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
176
+ * the click event. This version handles them immediately, and then prevents the
177
+ * following click event from propagating.
178
+ *
179
+ * Requires the {@link ngTouch `ngTouch`} module to be installed.
180
+ *
181
+ * This directive can fall back to using an ordinary click event, and so works on desktop
182
+ * browsers as well as mobile.
183
+ *
184
+ * This directive also sets the CSS class `ng-click-active` while the element is being held
185
+ * down (by a mouse click or touch) so you can restyle the depressed element if you wish.
186
+ *
187
+ * @element ANY
188
+ * @param {expression} ngClick {@link guide/expression Expression} to evaluate
189
+ * upon tap. (Event object is available as `$event`)
190
+ *
191
+ * @example
192
+ <doc:example>
193
+ <doc:source>
194
+ <button ng-click="count = count + 1" ng-init="count=0">
195
+ Increment
196
+ </button>
197
+ count: {{ count }}
198
+ </doc:source>
199
+ </doc:example>
200
+ */
201
+
202
+ ngTouch.config(['$provide', function($provide) {
203
+ $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
204
+ // drop the default ngClick directive
205
+ $delegate.shift();
206
+ return $delegate;
207
+ }]);
208
+ }]);
209
+
210
+ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
211
+ function($parse, $timeout, $rootElement) {
212
+ var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
213
+ var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
214
+ var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
215
+ var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
216
+
217
+ var ACTIVE_CLASS_NAME = 'ng-click-active';
218
+ var lastPreventedTime;
219
+ var touchCoordinates;
220
+
221
+
222
+ // TAP EVENTS AND GHOST CLICKS
223
+ //
224
+ // Why tap events?
225
+ // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
226
+ // double-tapping, and then fire a click event.
227
+ //
228
+ // This delay sucks and makes mobile apps feel unresponsive.
229
+ // So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
230
+ // the user has tapped on something.
231
+ //
232
+ // What happens when the browser then generates a click event?
233
+ // The browser, of course, also detects the tap and fires a click after a delay. This results in
234
+ // tapping/clicking twice. So we do "clickbusting" to prevent it.
235
+ //
236
+ // How does it work?
237
+ // We attach global touchstart and click handlers, that run during the capture (early) phase.
238
+ // So the sequence for a tap is:
239
+ // - global touchstart: Sets an "allowable region" at the point touched.
240
+ // - element's touchstart: Starts a touch
241
+ // (- touchmove or touchcancel ends the touch, no click follows)
242
+ // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
243
+ // too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
244
+ // - preventGhostClick() removes the allowable region the global touchstart created.
245
+ // - The browser generates a click event.
246
+ // - The global click handler catches the click, and checks whether it was in an allowable region.
247
+ // - If preventGhostClick was called, the region will have been removed, the click is busted.
248
+ // - If the region is still there, the click proceeds normally. Therefore clicks on links and
249
+ // other elements without ngTap on them work normally.
250
+ //
251
+ // This is an ugly, terrible hack!
252
+ // Yeah, tell me about it. The alternatives are using the slow click events, or making our users
253
+ // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
254
+ // encapsulates this ugly logic away from the user.
255
+ //
256
+ // Why not just put click handlers on the element?
257
+ // We do that too, just to be sure. The problem is that the tap event might have caused the DOM
258
+ // to change, so that the click fires in the same position but something else is there now. So
259
+ // the handlers are global and care only about coordinates and not elements.
260
+
261
+ // Checks if the coordinates are close enough to be within the region.
262
+ function hit(x1, y1, x2, y2) {
263
+ return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
264
+ }
265
+
266
+ // Checks a list of allowable regions against a click location.
267
+ // Returns true if the click should be allowed.
268
+ // Splices out the allowable region from the list after it has been used.
269
+ function checkAllowableRegions(touchCoordinates, x, y) {
270
+ for (var i = 0; i < touchCoordinates.length; i += 2) {
271
+ if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
272
+ touchCoordinates.splice(i, i + 2);
273
+ return true; // allowable region
274
+ }
275
+ }
276
+ return false; // No allowable region; bust it.
277
+ }
278
+
279
+ // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
280
+ // was called recently.
281
+ function onClick(event) {
282
+ if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
283
+ return; // Too old.
284
+ }
285
+
286
+ var touches = event.touches && event.touches.length ? event.touches : [event];
287
+ var x = touches[0].clientX;
288
+ var y = touches[0].clientY;
289
+ // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
290
+ // and on the input element). Depending on the exact browser, this second click we don't want
291
+ // to bust has either (0,0) or negative coordinates.
292
+ if (x < 1 && y < 1) {
293
+ return; // offscreen
294
+ }
295
+
296
+ // Look for an allowable region containing this click.
297
+ // If we find one, that means it was created by touchstart and not removed by
298
+ // preventGhostClick, so we don't bust it.
299
+ if (checkAllowableRegions(touchCoordinates, x, y)) {
300
+ return;
301
+ }
302
+
303
+ // If we didn't find an allowable region, bust the click.
304
+ event.stopPropagation();
305
+ event.preventDefault();
306
+
307
+ // Blur focused form elements
308
+ event.target && event.target.blur();
309
+ }
310
+
311
+
312
+ // Global touchstart handler that creates an allowable region for a click event.
313
+ // This allowable region can be removed by preventGhostClick if we want to bust it.
314
+ function onTouchStart(event) {
315
+ var touches = event.touches && event.touches.length ? event.touches : [event];
316
+ var x = touches[0].clientX;
317
+ var y = touches[0].clientY;
318
+ touchCoordinates.push(x, y);
319
+
320
+ $timeout(function() {
321
+ // Remove the allowable region.
322
+ for (var i = 0; i < touchCoordinates.length; i += 2) {
323
+ if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
324
+ touchCoordinates.splice(i, i + 2);
325
+ return;
326
+ }
327
+ }
328
+ }, PREVENT_DURATION, false);
329
+ }
330
+
331
+ // On the first call, attaches some event handlers. Then whenever it gets called, it creates a
332
+ // zone around the touchstart where clicks will get busted.
333
+ function preventGhostClick(x, y) {
334
+ if (!touchCoordinates) {
335
+ $rootElement[0].addEventListener('click', onClick, true);
336
+ $rootElement[0].addEventListener('touchstart', onTouchStart, true);
337
+ touchCoordinates = [];
338
+ }
339
+
340
+ lastPreventedTime = Date.now();
341
+
342
+ checkAllowableRegions(touchCoordinates, x, y);
343
+ }
344
+
345
+ // Actual linking function.
346
+ return function(scope, element, attr) {
347
+ var clickHandler = $parse(attr.ngClick),
348
+ tapping = false,
349
+ tapElement, // Used to blur the element after a tap.
350
+ startTime, // Used to check if the tap was held too long.
351
+ touchStartX,
352
+ touchStartY;
353
+
354
+ function resetState() {
355
+ tapping = false;
356
+ element.removeClass(ACTIVE_CLASS_NAME);
357
+ }
358
+
359
+ element.on('touchstart', function(event) {
360
+ tapping = true;
361
+ tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
362
+ // Hack for Safari, which can target text nodes instead of containers.
363
+ if(tapElement.nodeType == 3) {
364
+ tapElement = tapElement.parentNode;
365
+ }
366
+
367
+ element.addClass(ACTIVE_CLASS_NAME);
368
+
369
+ startTime = Date.now();
370
+
371
+ var touches = event.touches && event.touches.length ? event.touches : [event];
372
+ var e = touches[0].originalEvent || touches[0];
373
+ touchStartX = e.clientX;
374
+ touchStartY = e.clientY;
375
+ });
376
+
377
+ element.on('touchmove', function(event) {
378
+ resetState();
379
+ });
380
+
381
+ element.on('touchcancel', function(event) {
382
+ resetState();
383
+ });
384
+
385
+ element.on('touchend', function(event) {
386
+ var diff = Date.now() - startTime;
387
+
388
+ var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
389
+ ((event.touches && event.touches.length) ? event.touches : [event]);
390
+ var e = touches[0].originalEvent || touches[0];
391
+ var x = e.clientX;
392
+ var y = e.clientY;
393
+ var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
394
+
395
+ if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
396
+ // Call preventGhostClick so the clickbuster will catch the corresponding click.
397
+ preventGhostClick(x, y);
398
+
399
+ // Blur the focused element (the button, probably) before firing the callback.
400
+ // This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
401
+ // I couldn't get anything to work reliably on Android Chrome.
402
+ if (tapElement) {
403
+ tapElement.blur();
404
+ }
405
+
406
+ if (!angular.isDefined(attr.disabled) || attr.disabled === false) {
407
+ element.triggerHandler('click', [event]);
408
+ }
409
+ }
410
+
411
+ resetState();
412
+ });
413
+
414
+ // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
415
+ // something else nearby.
416
+ element.onclick = function(event) { };
417
+
418
+ // Actual click handler.
419
+ // There are three different kinds of clicks, only two of which reach this point.
420
+ // - On desktop browsers without touch events, their clicks will always come here.
421
+ // - On mobile browsers, the simulated "fast" click will call this.
422
+ // - But the browser's follow-up slow click will be "busted" before it reaches this handler.
423
+ // Therefore it's safe to use this directive on both mobile and desktop.
424
+ element.on('click', function(event, touchend) {
425
+ scope.$apply(function() {
426
+ clickHandler(scope, {$event: (touchend || event)});
427
+ });
428
+ });
429
+
430
+ element.on('mousedown', function(event) {
431
+ element.addClass(ACTIVE_CLASS_NAME);
432
+ });
433
+
434
+ element.on('mousemove mouseup', function(event) {
435
+ element.removeClass(ACTIVE_CLASS_NAME);
436
+ });
437
+
438
+ };
439
+ }]);
440
+
441
+ /* global ngTouch: false */
442
+
443
+ /**
444
+ * @ngdoc directive
445
+ * @name ngTouch.directive:ngSwipeLeft
446
+ *
447
+ * @description
448
+ * Specify custom behavior when an element is swiped to the left on a touchscreen device.
449
+ * A leftward swipe is a quick, right-to-left slide of the finger.
450
+ * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag
451
+ * too.
452
+ *
453
+ * Requires the {@link ngTouch `ngTouch`} module to be installed.
454
+ *
455
+ * @element ANY
456
+ * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate
457
+ * upon left swipe. (Event object is available as `$event`)
458
+ *
459
+ * @example
460
+ <doc:example>
461
+ <doc:source>
462
+ <div ng-show="!showActions" ng-swipe-left="showActions = true">
463
+ Some list content, like an email in the inbox
464
+ </div>
465
+ <div ng-show="showActions" ng-swipe-right="showActions = false">
466
+ <button ng-click="reply()">Reply</button>
467
+ <button ng-click="delete()">Delete</button>
468
+ </div>
469
+ </doc:source>
470
+ </doc:example>
471
+ */
472
+
473
+ /**
474
+ * @ngdoc directive
475
+ * @name ngTouch.directive:ngSwipeRight
476
+ *
477
+ * @description
478
+ * Specify custom behavior when an element is swiped to the right on a touchscreen device.
479
+ * A rightward swipe is a quick, left-to-right slide of the finger.
480
+ * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag
481
+ * too.
482
+ *
483
+ * Requires the {@link ngTouch `ngTouch`} module to be installed.
484
+ *
485
+ * @element ANY
486
+ * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate
487
+ * upon right swipe. (Event object is available as `$event`)
488
+ *
489
+ * @example
490
+ <doc:example>
491
+ <doc:source>
492
+ <div ng-show="!showActions" ng-swipe-left="showActions = true">
493
+ Some list content, like an email in the inbox
494
+ </div>
495
+ <div ng-show="showActions" ng-swipe-right="showActions = false">
496
+ <button ng-click="reply()">Reply</button>
497
+ <button ng-click="delete()">Delete</button>
498
+ </div>
499
+ </doc:source>
500
+ </doc:example>
501
+ */
502
+
503
+ function makeSwipeDirective(directiveName, direction, eventName) {
504
+ ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) {
505
+ // The maximum vertical delta for a swipe should be less than 75px.
506
+ var MAX_VERTICAL_DISTANCE = 75;
507
+ // Vertical distance should not be more than a fraction of the horizontal distance.
508
+ var MAX_VERTICAL_RATIO = 0.3;
509
+ // At least a 30px lateral motion is necessary for a swipe.
510
+ var MIN_HORIZONTAL_DISTANCE = 30;
511
+
512
+ return function(scope, element, attr) {
513
+ var swipeHandler = $parse(attr[directiveName]);
514
+
515
+ var startCoords, valid;
516
+
517
+ function validSwipe(coords) {
518
+ // Check that it's within the coordinates.
519
+ // Absolute vertical distance must be within tolerances.
520
+ // Horizontal distance, we take the current X - the starting X.
521
+ // This is negative for leftward swipes and positive for rightward swipes.
522
+ // After multiplying by the direction (-1 for left, +1 for right), legal swipes
523
+ // (ie. same direction as the directive wants) will have a positive delta and
524
+ // illegal ones a negative delta.
525
+ // Therefore this delta must be positive, and larger than the minimum.
526
+ if (!startCoords) return false;
527
+ var deltaY = Math.abs(coords.y - startCoords.y);
528
+ var deltaX = (coords.x - startCoords.x) * direction;
529
+ return valid && // Short circuit for already-invalidated swipes.
530
+ deltaY < MAX_VERTICAL_DISTANCE &&
531
+ deltaX > 0 &&
532
+ deltaX > MIN_HORIZONTAL_DISTANCE &&
533
+ deltaY / deltaX < MAX_VERTICAL_RATIO;
534
+ }
535
+
536
+ $swipe.bind(element, {
537
+ 'start': function(coords, event) {
538
+ startCoords = coords;
539
+ valid = true;
540
+ },
541
+ 'cancel': function(event) {
542
+ valid = false;
543
+ },
544
+ 'end': function(coords, event) {
545
+ if (validSwipe(coords)) {
546
+ scope.$apply(function() {
547
+ element.triggerHandler(eventName);
548
+ swipeHandler(scope, {$event: event});
549
+ });
550
+ }
551
+ }
552
+ });
553
+ };
554
+ }]);
555
+ }
556
+
557
+ // Left is negative X-coordinate, right is positive.
558
+ makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft');
559
+ makeSwipeDirective('ngSwipeRight', 1, 'swiperight');
560
+
561
+
562
+
563
+ })(window, window.angular);