angular-gem 1.2.24 → 1.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,584 @@
1
+ /**
2
+ * @license AngularJS v1.2.25
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);