angularjs-rails 1.5.0 → 1.5.5

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.
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) {