angular-gem 1.2.15 → 1.2.16

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