angularjs-rails 1.5.0 → 1.5.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b483abd42e39cf8cece41a4395e00cbbc1f47fa
4
- data.tar.gz: 6550da43653042c3a20cd931363ea937af6c2cc0
3
+ metadata.gz: 895ca6a972b4db80f33f25c4e63bb46a0b26432a
4
+ data.tar.gz: 3483320ceca41adcb1e22f2d1c3b9dcc47bc73aa
5
5
  SHA512:
6
- metadata.gz: fb396bd22d16510898e0bcd3055f0b9064f786be225b9bd49b9634df67f6865d0cb39c35ff394ac02ea73f1151adf9fc02bd871679d6ffacec1a50500a86df27
7
- data.tar.gz: 715cadcda48fb98ef800458f91c3c8b13e01015ddbb39b36526e97de5c33b84dd7d47c09f285c84726debdfe3df642b9ed44338ab09c9d6c7c11e6c019762782
6
+ metadata.gz: 8c023b142da5d9614c78886daffe753b5a0109ad9666ef7ab86cb3ffb14cfb5fc2918d0cd583eb1473fad8dcf9ca5c93d6cc4df350cae543386f625cc8157774
7
+ data.tar.gz: 53eb5be5f352da05f7fa7dad196d77546c19c1a878af64eb041d161a62ec81da9cae52c83c5258dd32d3716a61d988247b2b596a95b8d6130e891fbcb070b25e
@@ -1,6 +1,6 @@
1
1
  module AngularJS
2
2
  module Rails
3
- VERSION = "1.5.0"
4
- UNSTABLE_VERSION = "2.0.0-beta.6"
3
+ VERSION = "1.5.5"
4
+ UNSTABLE_VERSION = "2.0.0-beta.17"
5
5
  end
6
6
  end
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @license AngularJS v1.5.0
2
+ * @license AngularJS v1.5.5
3
3
  * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
- (function(window, angular, undefined) {'use strict';
6
+ (function(window, angular) {'use strict';
7
7
 
8
8
  /* jshint ignore:start */
9
9
  var noop = angular.noop;
@@ -2225,6 +2225,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2225
2225
  });
2226
2226
 
2227
2227
  rules.cancel.push(function(element, newAnimation, currentAnimation) {
2228
+ // cancel the animation if classes added / removed in both animation cancel each other out,
2229
+ // but only if the current animation isn't structural
2230
+
2231
+ if (currentAnimation.structural) return false;
2232
+
2228
2233
  var nA = newAnimation.addClass;
2229
2234
  var nR = newAnimation.removeClass;
2230
2235
  var cA = currentAnimation.addClass;
@@ -2312,7 +2317,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2312
2317
  }
2313
2318
 
2314
2319
  // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2315
- var contains = Node.prototype.contains || function(arg) {
2320
+ var contains = window.Node.prototype.contains || function(arg) {
2316
2321
  // jshint bitwise: false
2317
2322
  return this === arg || !!(this.compareDocumentPosition(arg) & 16);
2318
2323
  // jshint bitwise: true
@@ -2337,7 +2342,24 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2337
2342
  return matches;
2338
2343
  }
2339
2344
 
2340
- return {
2345
+ function filterFromRegistry(list, matchContainer, matchCallback) {
2346
+ var containerNode = extractElementNode(matchContainer);
2347
+ return list.filter(function(entry) {
2348
+ var isMatch = entry.node === containerNode &&
2349
+ (!matchCallback || entry.callback === matchCallback);
2350
+ return !isMatch;
2351
+ });
2352
+ }
2353
+
2354
+ function cleanupEventListeners(phase, element) {
2355
+ if (phase === 'close' && !element[0].parentNode) {
2356
+ // If the element is not attached to a parentNode, it has been removed by
2357
+ // the domOperation, and we can safely remove the event callbacks
2358
+ $animate.off(element);
2359
+ }
2360
+ }
2361
+
2362
+ var $animate = {
2341
2363
  on: function(event, container, callback) {
2342
2364
  var node = extractElementNode(container);
2343
2365
  callbackRegistry[event] = callbackRegistry[event] || [];
@@ -2345,24 +2367,36 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2345
2367
  node: node,
2346
2368
  callback: callback
2347
2369
  });
2370
+
2371
+ // Remove the callback when the element is removed from the DOM
2372
+ jqLite(container).on('$destroy', function() {
2373
+ var animationDetails = activeAnimationsLookup.get(node);
2374
+
2375
+ if (!animationDetails) {
2376
+ // If there's an animation ongoing, the callback calling code will remove
2377
+ // the event listeners. If we'd remove here, the callbacks would be removed
2378
+ // before the animation ends
2379
+ $animate.off(event, container, callback);
2380
+ }
2381
+ });
2348
2382
  },
2349
2383
 
2350
2384
  off: function(event, container, callback) {
2385
+ if (arguments.length === 1 && !angular.isString(arguments[0])) {
2386
+ container = arguments[0];
2387
+ for (var eventType in callbackRegistry) {
2388
+ callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container);
2389
+ }
2390
+
2391
+ return;
2392
+ }
2393
+
2351
2394
  var entries = callbackRegistry[event];
2352
2395
  if (!entries) return;
2353
2396
 
2354
2397
  callbackRegistry[event] = arguments.length === 1
2355
2398
  ? null
2356
2399
  : filterFromRegistry(entries, container, callback);
2357
-
2358
- function filterFromRegistry(list, matchContainer, matchCallback) {
2359
- var containerNode = extractElementNode(matchContainer);
2360
- return list.filter(function(entry) {
2361
- var isMatch = entry.node === containerNode &&
2362
- (!matchCallback || entry.callback === matchCallback);
2363
- return !isMatch;
2364
- });
2365
- }
2366
2400
  },
2367
2401
 
2368
2402
  pin: function(element, parentElement) {
@@ -2412,6 +2446,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2412
2446
  }
2413
2447
  };
2414
2448
 
2449
+ return $animate;
2450
+
2415
2451
  function queueAnimation(element, event, initialOptions) {
2416
2452
  // we always make a copy of the options since
2417
2453
  // there should never be any side effects on
@@ -2474,12 +2510,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2474
2510
 
2475
2511
  var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2476
2512
 
2513
+ var documentHidden = $document[0].hidden;
2514
+
2477
2515
  // this is a hard disable of all animations for the application or on
2478
2516
  // the element itself, therefore there is no need to continue further
2479
2517
  // past this point if not enabled
2480
2518
  // Animations are also disabled if the document is currently hidden (page is not visible
2481
2519
  // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
2482
- var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
2520
+ var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node);
2483
2521
  var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
2484
2522
  var hasExistingAnimation = !!existingAnimation.state;
2485
2523
 
@@ -2490,7 +2528,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2490
2528
  }
2491
2529
 
2492
2530
  if (skipAnimations) {
2531
+ // Callbacks should fire even if the document is hidden (regression fix for issue #14120)
2532
+ if (documentHidden) notifyProgress(runner, event, 'start');
2493
2533
  close();
2534
+ if (documentHidden) notifyProgress(runner, event, 'close');
2494
2535
  return runner;
2495
2536
  }
2496
2537
 
@@ -2640,6 +2681,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2640
2681
  markElementAnimationState(element, RUNNING_STATE);
2641
2682
  var realRunner = $$animation(element, event, animationDetails.options);
2642
2683
 
2684
+ // this will update the runner's flow-control events based on
2685
+ // the `realRunner` object.
2686
+ runner.setHost(realRunner);
2687
+ notifyProgress(runner, event, 'start', {});
2688
+
2643
2689
  realRunner.done(function(status) {
2644
2690
  close(!status);
2645
2691
  var animationDetails = activeAnimationsLookup.get(node);
@@ -2648,11 +2694,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2648
2694
  }
2649
2695
  notifyProgress(runner, event, 'close', {});
2650
2696
  });
2651
-
2652
- // this will update the runner's flow-control events based on
2653
- // the `realRunner` object.
2654
- runner.setHost(realRunner);
2655
- notifyProgress(runner, event, 'start', {});
2656
2697
  });
2657
2698
 
2658
2699
  return runner;
@@ -2669,7 +2710,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2669
2710
  forEach(callbacks, function(callback) {
2670
2711
  callback(element, phase, data);
2671
2712
  });
2713
+ cleanupEventListeners(phase, element);
2672
2714
  });
2715
+ } else {
2716
+ cleanupEventListeners(phase, element);
2673
2717
  }
2674
2718
  });
2675
2719
  runner.progress(event, phase, data);
@@ -2728,30 +2772,31 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2728
2772
  var animateChildren;
2729
2773
  var elementDisabled = disabledElementsLookup.get(getDomNode(element));
2730
2774
 
2731
- var parentHost = element.data(NG_ANIMATE_PIN_DATA);
2775
+ var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
2732
2776
  if (parentHost) {
2733
2777
  parentElement = parentHost;
2734
2778
  }
2735
2779
 
2736
- while (parentElement && parentElement.length) {
2780
+ parentElement = getDomNode(parentElement);
2781
+
2782
+ while (parentElement) {
2737
2783
  if (!rootElementDetected) {
2738
2784
  // angular doesn't want to attempt to animate elements outside of the application
2739
2785
  // therefore we need to ensure that the rootElement is an ancestor of the current element
2740
2786
  rootElementDetected = isMatchingElement(parentElement, $rootElement);
2741
2787
  }
2742
2788
 
2743
- var parentNode = parentElement[0];
2744
- if (parentNode.nodeType !== ELEMENT_NODE) {
2789
+ if (parentElement.nodeType !== ELEMENT_NODE) {
2745
2790
  // no point in inspecting the #document element
2746
2791
  break;
2747
2792
  }
2748
2793
 
2749
- var details = activeAnimationsLookup.get(parentNode) || {};
2794
+ var details = activeAnimationsLookup.get(parentElement) || {};
2750
2795
  // either an enter, leave or move animation will commence
2751
2796
  // therefore we can't allow any animations to take place
2752
2797
  // but if a parent animation is class-based then that's ok
2753
2798
  if (!parentAnimationDetected) {
2754
- var parentElementDisabled = disabledElementsLookup.get(parentNode);
2799
+ var parentElementDisabled = disabledElementsLookup.get(parentElement);
2755
2800
 
2756
2801
  if (parentElementDisabled === true && elementDisabled !== false) {
2757
2802
  // disable animations if the user hasn't explicitly enabled animations on the
@@ -2766,7 +2811,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2766
2811
  }
2767
2812
 
2768
2813
  if (isUndefined(animateChildren) || animateChildren === true) {
2769
- var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
2814
+ var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
2770
2815
  if (isDefined(value)) {
2771
2816
  animateChildren = value;
2772
2817
  }
@@ -2789,15 +2834,15 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2789
2834
 
2790
2835
  if (!rootElementDetected) {
2791
2836
  // If no rootElement is detected, check if the parentElement is pinned to another element
2792
- parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
2837
+ parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
2793
2838
  if (parentHost) {
2794
2839
  // The pin target element becomes the next parent element
2795
- parentElement = parentHost;
2840
+ parentElement = getDomNode(parentHost);
2796
2841
  continue;
2797
2842
  }
2798
2843
  }
2799
2844
 
2800
- parentElement = parentElement.parent();
2845
+ parentElement = parentElement.parentNode;
2801
2846
  }
2802
2847
 
2803
2848
  var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
@@ -3238,11 +3283,17 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3238
3283
  *
3239
3284
  * ngAnimateSwap is a animation-oriented directive that allows for the container to
3240
3285
  * be removed and entered in whenever the associated expression changes. A
3241
- * common usecase for this directive is a rotating banner component which
3286
+ * common usecase for this directive is a rotating banner or slider component which
3242
3287
  * contains one image being present at a time. When the active image changes
3243
3288
  * then the old image will perform a `leave` animation and the new element
3244
3289
  * will be inserted via an `enter` animation.
3245
3290
  *
3291
+ * @animations
3292
+ * | Animation | Occurs |
3293
+ * |----------------------------------|--------------------------------------|
3294
+ * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
3295
+ * | {@link ng.$animate#leave leave} | when the old element is removed from the DOM |
3296
+ *
3246
3297
  * @example
3247
3298
  * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
3248
3299
  * deps="angular-animate.js"
@@ -3467,7 +3518,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
3467
3518
  * <div ng-show="bool" class="fade">
3468
3519
  * Show and hide me
3469
3520
  * </div>
3470
- * <button ng-click="bool=true">Toggle</button>
3521
+ * <button ng-click="bool=!bool">Toggle</button>
3471
3522
  *
3472
3523
  * <style>
3473
3524
  * .fade.ng-hide {
@@ -4036,31 +4087,6 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
4036
4087
  * possible be sure to visit the {@link ng.$animate $animate service API page}.
4037
4088
  *
4038
4089
  *
4039
- * ### Preventing Collisions With Third Party Libraries
4040
- *
4041
- * Some third-party frameworks place animation duration defaults across many element or className
4042
- * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
4043
- * is expecting actual animations on these elements and has to wait for their completion.
4044
- *
4045
- * You can prevent this unwanted behavior by using a prefix on all your animation classes:
4046
- *
4047
- * ```css
4048
- * /&#42; prefixed with animate- &#42;/
4049
- * .animate-fade-add.animate-fade-add-active {
4050
- * transition:1s linear all;
4051
- * opacity:0;
4052
- * }
4053
- * ```
4054
- *
4055
- * You then configure `$animate` to enforce this prefix:
4056
- *
4057
- * ```js
4058
- * $animateProvider.classNameFilter(/animate-/);
4059
- * ```
4060
- *
4061
- * This also may provide your application with a speed boost since only specific elements containing CSS class prefix
4062
- * will be evaluated for animation when any DOM changes occur in the application.
4063
- *
4064
4090
  * ## Callbacks and Promises
4065
4091
  *
4066
4092
  * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @license AngularJS v1.5.0
2
+ * @license AngularJS v1.5.5
3
3
  * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
- (function(window, angular, undefined) {'use strict';
6
+ (function(window, angular) {'use strict';
7
7
 
8
8
  /**
9
9
  * @ngdoc module
@@ -21,7 +21,7 @@
21
21
  *
22
22
  * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
23
23
  * directives are supported:
24
- * `ngModel`, `ngChecked`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
24
+ * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
25
25
  * `ngDblClick`, and `ngMessages`.
26
26
  *
27
27
  * Below is a more detailed breakdown of the attributes handled by ngAria:
@@ -30,8 +30,9 @@
30
30
  * |---------------------------------------------|----------------------------------------------------------------------------------------|
31
31
  * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
32
32
  * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
33
- * | {@link ng.directive:ngRequired ngRequired} | aria-required |
34
- * | {@link ng.directive:ngChecked ngChecked} | aria-checked |
33
+ * | {@link ng.directive:ngRequired ngRequired} | aria-required
34
+ * | {@link ng.directive:ngChecked ngChecked} | aria-checked
35
+ * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly ||
35
36
  * | {@link ng.directive:ngValue ngValue} | aria-checked |
36
37
  * | {@link ng.directive:ngShow ngShow} | aria-hidden |
37
38
  * | {@link ng.directive:ngHide ngHide} | aria-hidden |
@@ -96,6 +97,7 @@ function $AriaProvider() {
96
97
  var config = {
97
98
  ariaHidden: true,
98
99
  ariaChecked: true,
100
+ ariaReadonly: true,
99
101
  ariaDisabled: true,
100
102
  ariaRequired: true,
101
103
  ariaInvalid: true,
@@ -113,6 +115,7 @@ function $AriaProvider() {
113
115
  *
114
116
  * - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags
115
117
  * - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags
118
+ * - **ariaReadonly** – `{boolean}` – Enables/disables aria-readonly tags
116
119
  * - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags
117
120
  * - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags
118
121
  * - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags
@@ -175,6 +178,7 @@ function $AriaProvider() {
175
178
  * The full list of directives that interface with ngAria:
176
179
  * * **ngModel**
177
180
  * * **ngChecked**
181
+ * * **ngReadonly**
178
182
  * * **ngRequired**
179
183
  * * **ngDisabled**
180
184
  * * **ngValue**
@@ -214,6 +218,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
214
218
  .directive('ngChecked', ['$aria', function($aria) {
215
219
  return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false);
216
220
  }])
221
+ .directive('ngReadonly', ['$aria', function($aria) {
222
+ return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false);
223
+ }])
217
224
  .directive('ngRequired', ['$aria', function($aria) {
218
225
  return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false);
219
226
  }])
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @license AngularJS v1.5.0
2
+ * @license AngularJS v1.5.5
3
3
  * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
- (function(window, angular, undefined) {'use strict';
6
+ (function(window, angular) {'use strict';
7
7
 
8
8
  /**
9
9
  * @ngdoc module
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.5.0
2
+ * @license AngularJS v1.5.5
3
3
  * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -87,7 +87,7 @@ function minErr(module, ErrorConstructor) {
87
87
  return match;
88
88
  });
89
89
 
90
- message += '\nhttp://errors.angularjs.org/1.5.0/' +
90
+ message += '\nhttp://errors.angularjs.org/1.5.5/' +
91
91
  (module ? module + '/' : '') + code;
92
92
 
93
93
  for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
@@ -296,9 +296,9 @@ function setupModuleLoader(window) {
296
296
  * @ngdoc method
297
297
  * @name angular.Module#decorator
298
298
  * @module ng
299
- * @param {string} The name of the service to decorate.
300
- * @param {Function} This function will be invoked when the service needs to be
301
- * instantiated and should return the decorated service instance.
299
+ * @param {string} name The name of the service to decorate.
300
+ * @param {Function} decorFn This function will be invoked when the service needs to be
301
+ * instantiated and should return the decorated service instance.
302
302
  * @description
303
303
  * See {@link auto.$provide#decorator $provide.decorator()}.
304
304
  */
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @license AngularJS v1.5.0
2
+ * @license AngularJS v1.5.5
3
3
  * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
- (function(window, angular, undefined) {'use strict';
6
+ (function(window, angular) {'use strict';
7
7
 
8
8
  // NOTE: ADVANCED_OPTIMIZATIONS mode.
9
9
  //
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @license AngularJS v1.5.0
2
+ * @license AngularJS v1.5.5
3
3
  * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
- (function(window, angular, undefined) {'use strict';
6
+ (function(window, angular) {'use strict';
7
7
 
8
8
  /* jshint ignore:start */
9
9
  // this code is in the core, but not in angular-messages.js
@@ -29,45 +29,66 @@ var jqLite = angular.element;
29
29
  * `ngMessage` and `ngMessageExp` directives.
30
30
  *
31
31
  * # Usage
32
- * The `ngMessages` directive listens on a key/value collection which is set on the ngMessages attribute.
33
- * Since the {@link ngModel ngModel} directive exposes an `$error` object, this error object can be
34
- * used with `ngMessages` to display control error messages in an easier way than with just regular angular
35
- * template directives.
32
+ * The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
33
+ * (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use
34
+ * case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the
35
+ * {@link ngModel ngModel} directive.
36
+ *
37
+ * The child elements of the `ngMessages` directive are matched to the collection keys by a `ngMessage` or
38
+ * `ngMessageExp` directive. The value of these attributes must match a key in the collection that is provided by
39
+ * the `ngMessages` directive.
40
+ *
41
+ * Consider the following example, which illustrates a typical use case of `ngMessages`. Within the form `myForm` we
42
+ * have a text input named `myField` which is bound to the scope variable `field` using the {@link ngModel ngModel}
43
+ * directive.
44
+ *
45
+ * The `myField` field is a required input of type `email` with a maximum length of 15 characters.
36
46
  *
37
47
  * ```html
38
48
  * <form name="myForm">
39
49
  * <label>
40
50
  * Enter text:
41
- * <input type="text" ng-model="field" name="myField" required minlength="5" />
51
+ * <input type="email" ng-model="field" name="myField" required maxlength="15" />
42
52
  * </label>
43
53
  * <div ng-messages="myForm.myField.$error" role="alert">
44
- * <div ng-message="required">You did not enter a field</div>
45
- * <div ng-message="minlength, maxlength">
46
- * Your email must be between 5 and 100 characters long
47
- * </div>
54
+ * <div ng-message="required">Please enter a value for this field.</div>
55
+ * <div ng-message="email">This field must be a valid email address.</div>
56
+ * <div ng-message="maxlength">This field can be at most 15 characters long.</div>
48
57
  * </div>
49
58
  * </form>
50
59
  * ```
51
60
  *
52
- * Now whatever key/value entries are present within the provided object (in this case `$error`) then
53
- * the ngMessages directive will render the inner first ngMessage directive (depending if the key values
54
- * match the attribute value present on each ngMessage directive). In other words, if your errors
55
- * object contains the following data:
61
+ * In order to show error messages corresponding to `myField` we first create an element with an `ngMessages` attribute
62
+ * set to the `$error` object owned by the `myField` input in our `myForm` form.
63
+ *
64
+ * Within this element we then create separate elements for each of the possible errors that `myField` could have.
65
+ * The `ngMessage` attribute is used to declare which element(s) will appear for which error - for example,
66
+ * setting `ng-message="required"` specifies that this particular element should be displayed when there
67
+ * is no value present for the required field `myField` (because the key `required` will be `true` in the object
68
+ * `myForm.myField.$error`).
69
+ *
70
+ * ### Message order
71
+ *
72
+ * By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more
73
+ * than one message (or error) key is currently true, then which message is shown is determined by the order of messages
74
+ * in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have
75
+ * to prioritise messages using custom JavaScript code.
76
+ *
77
+ * Given the following error object for our example (which informs us that the field `myField` currently has both the
78
+ * `required` and `email` errors):
56
79
  *
57
80
  * ```javascript
58
81
  * <!-- keep in mind that ngModel automatically sets these error flags -->
59
- * myField.$error = { minlength : true, required : true };
82
+ * myField.$error = { required : true, email: true, maxlength: false };
60
83
  * ```
84
+ * The `required` message will be displayed to the user since it appears before the `email` message in the DOM.
85
+ * Once the user types a single character, the `required` message will disappear (since the field now has a value)
86
+ * but the `email` message will be visible because it is still applicable.
61
87
  *
62
- * Then the `required` message will be displayed first. When required is false then the `minlength` message
63
- * will be displayed right after (since these messages are ordered this way in the template HTML code).
64
- * The prioritization of each message is determined by what order they're present in the DOM.
65
- * Therefore, instead of having custom JavaScript code determine the priority of what errors are
66
- * present before others, the presentation of the errors are handled within the template.
88
+ * ### Displaying multiple messages at the same time
67
89
  *
68
- * By default, ngMessages will only display one error at a time. However, if you wish to display all
69
- * messages then the `ng-messages-multiple` attribute flag can be used on the element containing the
70
- * ngMessages directive to make this happen.
90
+ * While `ngMessages` will by default only display one error element at a time, the `ng-messages-multiple` attribute can
91
+ * be applied to the `ngMessages` container element to cause it to display all applicable error messages at once:
71
92
  *
72
93
  * ```html
73
94
  * <!-- attribute-style usage -->
@@ -394,6 +415,13 @@ angular.module('ngMessages', [])
394
415
 
395
416
  $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
396
417
 
418
+ // If the element is destroyed, proactively destroy all the currently visible messages
419
+ $element.on('$destroy', function() {
420
+ forEach(messages, function(item) {
421
+ item.message.detach();
422
+ });
423
+ });
424
+
397
425
  this.reRender = function() {
398
426
  if (!renderLater) {
399
427
  renderLater = true;
@@ -428,6 +456,7 @@ angular.module('ngMessages', [])
428
456
  function findPreviousMessage(parent, comment) {
429
457
  var prevNode = comment;
430
458
  var parentLookup = [];
459
+
431
460
  while (prevNode && prevNode !== parent) {
432
461
  var prevKey = prevNode.$$ngMessageNode;
433
462
  if (prevKey && prevKey.length) {
@@ -439,8 +468,11 @@ angular.module('ngMessages', [])
439
468
  if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) == -1) {
440
469
  parentLookup.push(prevNode);
441
470
  prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
471
+ } else if (prevNode.previousSibling) {
472
+ prevNode = prevNode.previousSibling;
442
473
  } else {
443
- prevNode = prevNode.previousSibling || prevNode.parentNode;
474
+ prevNode = prevNode.parentNode;
475
+ parentLookup.push(prevNode);
444
476
  }
445
477
  }
446
478
  }
@@ -527,7 +559,10 @@ angular.module('ngMessages', [])
527
559
  element.after(contents);
528
560
 
529
561
  // the anchor is placed for debugging purposes
530
- var anchor = jqLite($document[0].createComment(' ngMessagesInclude: ' + src + ' '));
562
+ var comment = $compile.$$createComment ?
563
+ $compile.$$createComment('ngMessagesInclude', src) :
564
+ $document[0].createComment(' ngMessagesInclude: ' + src + ' ');
565
+ var anchor = jqLite(comment);
531
566
  element.after(anchor);
532
567
 
533
568
  // we don't want to pollute the DOM anymore by keeping an empty directive element
@@ -650,8 +685,8 @@ function ngMessageDirectiveFactory() {
650
685
  // when we are destroying the node later.
651
686
  var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
652
687
 
653
- // in the event that the parent element is destroyed
654
- // by any other structural directive then it's time
688
+ // in the event that the element or a parent element is destroyed
689
+ // by another structural directive then it's time
655
690
  // to deregister the message from the controller
656
691
  currentElement.on('$destroy', function() {
657
692
  if (currentElement && currentElement.$$attachId === $$attachId) {