angular-gem 1.2.13 → 1.2.14

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