angular-gem 1.2.19 → 1.2.20

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,584 @@
1
+ /**
2
+ * @license AngularJS v1.2.20
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 module="ngClickExample" deps="angular-touch.js">
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
+ <file name="script.js">
198
+ angular.module('ngClickExample', ['ngTouch']);
199
+ </file>
200
+ </example>
201
+ */
202
+
203
+ ngTouch.config(['$provide', function($provide) {
204
+ $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
205
+ // drop the default ngClick directive
206
+ $delegate.shift();
207
+ return $delegate;
208
+ }]);
209
+ }]);
210
+
211
+ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
212
+ function($parse, $timeout, $rootElement) {
213
+ var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
214
+ var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
215
+ var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
216
+ var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
217
+
218
+ var ACTIVE_CLASS_NAME = 'ng-click-active';
219
+ var lastPreventedTime;
220
+ var touchCoordinates;
221
+ var lastLabelClickCoordinates;
222
+
223
+
224
+ // TAP EVENTS AND GHOST CLICKS
225
+ //
226
+ // Why tap events?
227
+ // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
228
+ // double-tapping, and then fire a click event.
229
+ //
230
+ // This delay sucks and makes mobile apps feel unresponsive.
231
+ // So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
232
+ // the user has tapped on something.
233
+ //
234
+ // What happens when the browser then generates a click event?
235
+ // The browser, of course, also detects the tap and fires a click after a delay. This results in
236
+ // tapping/clicking twice. We do "clickbusting" to prevent it.
237
+ //
238
+ // How does it work?
239
+ // We attach global touchstart and click handlers, that run during the capture (early) phase.
240
+ // So the sequence for a tap is:
241
+ // - global touchstart: Sets an "allowable region" at the point touched.
242
+ // - element's touchstart: Starts a touch
243
+ // (- touchmove or touchcancel ends the touch, no click follows)
244
+ // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
245
+ // too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
246
+ // - preventGhostClick() removes the allowable region the global touchstart created.
247
+ // - The browser generates a click event.
248
+ // - The global click handler catches the click, and checks whether it was in an allowable region.
249
+ // - If preventGhostClick was called, the region will have been removed, the click is busted.
250
+ // - If the region is still there, the click proceeds normally. Therefore clicks on links and
251
+ // other elements without ngTap on them work normally.
252
+ //
253
+ // This is an ugly, terrible hack!
254
+ // Yeah, tell me about it. The alternatives are using the slow click events, or making our users
255
+ // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
256
+ // encapsulates this ugly logic away from the user.
257
+ //
258
+ // Why not just put click handlers on the element?
259
+ // We do that too, just to be sure. If the tap event caused the DOM to change,
260
+ // it is possible another element is now in that position. To take account for these possibly
261
+ // distinct elements, the handlers are global and care only about coordinates.
262
+
263
+ // Checks if the coordinates are close enough to be within the region.
264
+ function hit(x1, y1, x2, y2) {
265
+ return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
266
+ }
267
+
268
+ // Checks a list of allowable regions against a click location.
269
+ // Returns true if the click should be allowed.
270
+ // Splices out the allowable region from the list after it has been used.
271
+ function checkAllowableRegions(touchCoordinates, x, y) {
272
+ for (var i = 0; i < touchCoordinates.length; i += 2) {
273
+ if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
274
+ touchCoordinates.splice(i, i + 2);
275
+ return true; // allowable region
276
+ }
277
+ }
278
+ return false; // No allowable region; bust it.
279
+ }
280
+
281
+ // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
282
+ // was called recently.
283
+ function onClick(event) {
284
+ if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
285
+ return; // Too old.
286
+ }
287
+
288
+ var touches = event.touches && event.touches.length ? event.touches : [event];
289
+ var x = touches[0].clientX;
290
+ var y = touches[0].clientY;
291
+ // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
292
+ // and on the input element). Depending on the exact browser, this second click we don't want
293
+ // to bust has either (0,0), negative coordinates, or coordinates equal to triggering label
294
+ // click event
295
+ if (x < 1 && y < 1) {
296
+ return; // offscreen
297
+ }
298
+ if (lastLabelClickCoordinates &&
299
+ lastLabelClickCoordinates[0] === x && lastLabelClickCoordinates[1] === y) {
300
+ return; // input click triggered by label click
301
+ }
302
+ // reset label click coordinates on first subsequent click
303
+ if (lastLabelClickCoordinates) {
304
+ lastLabelClickCoordinates = null;
305
+ }
306
+ // remember label click coordinates to prevent click busting of trigger click event on input
307
+ if (event.target.tagName.toLowerCase() === 'label') {
308
+ lastLabelClickCoordinates = [x, y];
309
+ }
310
+
311
+ // Look for an allowable region containing this click.
312
+ // If we find one, that means it was created by touchstart and not removed by
313
+ // preventGhostClick, so we don't bust it.
314
+ if (checkAllowableRegions(touchCoordinates, x, y)) {
315
+ return;
316
+ }
317
+
318
+ // If we didn't find an allowable region, bust the click.
319
+ event.stopPropagation();
320
+ event.preventDefault();
321
+
322
+ // Blur focused form elements
323
+ event.target && event.target.blur();
324
+ }
325
+
326
+
327
+ // Global touchstart handler that creates an allowable region for a click event.
328
+ // This allowable region can be removed by preventGhostClick if we want to bust it.
329
+ function onTouchStart(event) {
330
+ var touches = event.touches && event.touches.length ? event.touches : [event];
331
+ var x = touches[0].clientX;
332
+ var y = touches[0].clientY;
333
+ touchCoordinates.push(x, y);
334
+
335
+ $timeout(function() {
336
+ // Remove the allowable region.
337
+ for (var i = 0; i < touchCoordinates.length; i += 2) {
338
+ if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
339
+ touchCoordinates.splice(i, i + 2);
340
+ return;
341
+ }
342
+ }
343
+ }, PREVENT_DURATION, false);
344
+ }
345
+
346
+ // On the first call, attaches some event handlers. Then whenever it gets called, it creates a
347
+ // zone around the touchstart where clicks will get busted.
348
+ function preventGhostClick(x, y) {
349
+ if (!touchCoordinates) {
350
+ $rootElement[0].addEventListener('click', onClick, true);
351
+ $rootElement[0].addEventListener('touchstart', onTouchStart, true);
352
+ touchCoordinates = [];
353
+ }
354
+
355
+ lastPreventedTime = Date.now();
356
+
357
+ checkAllowableRegions(touchCoordinates, x, y);
358
+ }
359
+
360
+ // Actual linking function.
361
+ return function(scope, element, attr) {
362
+ var clickHandler = $parse(attr.ngClick),
363
+ tapping = false,
364
+ tapElement, // Used to blur the element after a tap.
365
+ startTime, // Used to check if the tap was held too long.
366
+ touchStartX,
367
+ touchStartY;
368
+
369
+ function resetState() {
370
+ tapping = false;
371
+ element.removeClass(ACTIVE_CLASS_NAME);
372
+ }
373
+
374
+ element.on('touchstart', function(event) {
375
+ tapping = true;
376
+ tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
377
+ // Hack for Safari, which can target text nodes instead of containers.
378
+ if(tapElement.nodeType == 3) {
379
+ tapElement = tapElement.parentNode;
380
+ }
381
+
382
+ element.addClass(ACTIVE_CLASS_NAME);
383
+
384
+ startTime = Date.now();
385
+
386
+ var touches = event.touches && event.touches.length ? event.touches : [event];
387
+ var e = touches[0].originalEvent || touches[0];
388
+ touchStartX = e.clientX;
389
+ touchStartY = e.clientY;
390
+ });
391
+
392
+ element.on('touchmove', function(event) {
393
+ resetState();
394
+ });
395
+
396
+ element.on('touchcancel', function(event) {
397
+ resetState();
398
+ });
399
+
400
+ element.on('touchend', function(event) {
401
+ var diff = Date.now() - startTime;
402
+
403
+ var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
404
+ ((event.touches && event.touches.length) ? event.touches : [event]);
405
+ var e = touches[0].originalEvent || touches[0];
406
+ var x = e.clientX;
407
+ var y = e.clientY;
408
+ var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
409
+
410
+ if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
411
+ // Call preventGhostClick so the clickbuster will catch the corresponding click.
412
+ preventGhostClick(x, y);
413
+
414
+ // Blur the focused element (the button, probably) before firing the callback.
415
+ // This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
416
+ // I couldn't get anything to work reliably on Android Chrome.
417
+ if (tapElement) {
418
+ tapElement.blur();
419
+ }
420
+
421
+ if (!angular.isDefined(attr.disabled) || attr.disabled === false) {
422
+ element.triggerHandler('click', [event]);
423
+ }
424
+ }
425
+
426
+ resetState();
427
+ });
428
+
429
+ // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
430
+ // something else nearby.
431
+ element.onclick = function(event) { };
432
+
433
+ // Actual click handler.
434
+ // There are three different kinds of clicks, only two of which reach this point.
435
+ // - On desktop browsers without touch events, their clicks will always come here.
436
+ // - On mobile browsers, the simulated "fast" click will call this.
437
+ // - But the browser's follow-up slow click will be "busted" before it reaches this handler.
438
+ // Therefore it's safe to use this directive on both mobile and desktop.
439
+ element.on('click', function(event, touchend) {
440
+ scope.$apply(function() {
441
+ clickHandler(scope, {$event: (touchend || event)});
442
+ });
443
+ });
444
+
445
+ element.on('mousedown', function(event) {
446
+ element.addClass(ACTIVE_CLASS_NAME);
447
+ });
448
+
449
+ element.on('mousemove mouseup', function(event) {
450
+ element.removeClass(ACTIVE_CLASS_NAME);
451
+ });
452
+
453
+ };
454
+ }]);
455
+
456
+ /* global ngTouch: false */
457
+
458
+ /**
459
+ * @ngdoc directive
460
+ * @name ngSwipeLeft
461
+ *
462
+ * @description
463
+ * Specify custom behavior when an element is swiped to the left on a touchscreen device.
464
+ * A leftward swipe is a quick, right-to-left slide of the finger.
465
+ * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag
466
+ * too.
467
+ *
468
+ * Requires the {@link ngTouch `ngTouch`} module to be installed.
469
+ *
470
+ * @element ANY
471
+ * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate
472
+ * upon left swipe. (Event object is available as `$event`)
473
+ *
474
+ * @example
475
+ <example module="ngSwipeLeftExample" deps="angular-touch.js">
476
+ <file name="index.html">
477
+ <div ng-show="!showActions" ng-swipe-left="showActions = true">
478
+ Some list content, like an email in the inbox
479
+ </div>
480
+ <div ng-show="showActions" ng-swipe-right="showActions = false">
481
+ <button ng-click="reply()">Reply</button>
482
+ <button ng-click="delete()">Delete</button>
483
+ </div>
484
+ </file>
485
+ <file name="script.js">
486
+ angular.module('ngSwipeLeftExample', ['ngTouch']);
487
+ </file>
488
+ </example>
489
+ */
490
+
491
+ /**
492
+ * @ngdoc directive
493
+ * @name ngSwipeRight
494
+ *
495
+ * @description
496
+ * Specify custom behavior when an element is swiped to the right on a touchscreen device.
497
+ * A rightward swipe is a quick, left-to-right slide of the finger.
498
+ * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag
499
+ * too.
500
+ *
501
+ * Requires the {@link ngTouch `ngTouch`} module to be installed.
502
+ *
503
+ * @element ANY
504
+ * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate
505
+ * upon right swipe. (Event object is available as `$event`)
506
+ *
507
+ * @example
508
+ <example module="ngSwipeRightExample" deps="angular-touch.js">
509
+ <file name="index.html">
510
+ <div ng-show="!showActions" ng-swipe-left="showActions = true">
511
+ Some list content, like an email in the inbox
512
+ </div>
513
+ <div ng-show="showActions" ng-swipe-right="showActions = false">
514
+ <button ng-click="reply()">Reply</button>
515
+ <button ng-click="delete()">Delete</button>
516
+ </div>
517
+ </file>
518
+ <file name="script.js">
519
+ angular.module('ngSwipeRightExample', ['ngTouch']);
520
+ </file>
521
+ </example>
522
+ */
523
+
524
+ function makeSwipeDirective(directiveName, direction, eventName) {
525
+ ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) {
526
+ // The maximum vertical delta for a swipe should be less than 75px.
527
+ var MAX_VERTICAL_DISTANCE = 75;
528
+ // Vertical distance should not be more than a fraction of the horizontal distance.
529
+ var MAX_VERTICAL_RATIO = 0.3;
530
+ // At least a 30px lateral motion is necessary for a swipe.
531
+ var MIN_HORIZONTAL_DISTANCE = 30;
532
+
533
+ return function(scope, element, attr) {
534
+ var swipeHandler = $parse(attr[directiveName]);
535
+
536
+ var startCoords, valid;
537
+
538
+ function validSwipe(coords) {
539
+ // Check that it's within the coordinates.
540
+ // Absolute vertical distance must be within tolerances.
541
+ // Horizontal distance, we take the current X - the starting X.
542
+ // This is negative for leftward swipes and positive for rightward swipes.
543
+ // After multiplying by the direction (-1 for left, +1 for right), legal swipes
544
+ // (ie. same direction as the directive wants) will have a positive delta and
545
+ // illegal ones a negative delta.
546
+ // Therefore this delta must be positive, and larger than the minimum.
547
+ if (!startCoords) return false;
548
+ var deltaY = Math.abs(coords.y - startCoords.y);
549
+ var deltaX = (coords.x - startCoords.x) * direction;
550
+ return valid && // Short circuit for already-invalidated swipes.
551
+ deltaY < MAX_VERTICAL_DISTANCE &&
552
+ deltaX > 0 &&
553
+ deltaX > MIN_HORIZONTAL_DISTANCE &&
554
+ deltaY / deltaX < MAX_VERTICAL_RATIO;
555
+ }
556
+
557
+ $swipe.bind(element, {
558
+ 'start': function(coords, event) {
559
+ startCoords = coords;
560
+ valid = true;
561
+ },
562
+ 'cancel': function(event) {
563
+ valid = false;
564
+ },
565
+ 'end': function(coords, event) {
566
+ if (validSwipe(coords)) {
567
+ scope.$apply(function() {
568
+ element.triggerHandler(eventName);
569
+ swipeHandler(scope, {$event: event});
570
+ });
571
+ }
572
+ }
573
+ });
574
+ };
575
+ }]);
576
+ }
577
+
578
+ // Left is negative X-coordinate, right is positive.
579
+ makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft');
580
+ makeSwipeDirective('ngSwipeRight', 1, 'swiperight');
581
+
582
+
583
+
584
+ })(window, window.angular);