rsanchez
2014-12-11 36edce38f6b17f73322fa38404d6e01818a44fd2
securis/src/main/webapp/js/angular/angular.js
....@@ -1,5 +1,5 @@
11 /**
2
- * @license AngularJS v1.3.0
2
+ * @license AngularJS v1.3.6
33 * (c) 2010-2014 Google, Inc. http://angularjs.org
44 * License: MIT
55 */
....@@ -37,45 +37,28 @@
3737
3838 function minErr(module, ErrorConstructor) {
3939 ErrorConstructor = ErrorConstructor || Error;
40
- return function () {
40
+ return function() {
4141 var code = arguments[0],
4242 prefix = '[' + (module ? module + ':' : '') + code + '] ',
4343 template = arguments[1],
4444 templateArgs = arguments,
45
- stringify = function (obj) {
46
- if (typeof obj === 'function') {
47
- return obj.toString().replace(/ \{[\s\S]*$/, '');
48
- } else if (typeof obj === 'undefined') {
49
- return 'undefined';
50
- } else if (typeof obj !== 'string') {
51
- return JSON.stringify(obj);
52
- }
53
- return obj;
54
- },
45
+
5546 message, i;
5647
57
- message = prefix + template.replace(/\{\d+\}/g, function (match) {
48
+ message = prefix + template.replace(/\{\d+\}/g, function(match) {
5849 var index = +match.slice(1, -1), arg;
5950
6051 if (index + 2 < templateArgs.length) {
61
- arg = templateArgs[index + 2];
62
- if (typeof arg === 'function') {
63
- return arg.toString().replace(/ ?\{[\s\S]*$/, '');
64
- } else if (typeof arg === 'undefined') {
65
- return 'undefined';
66
- } else if (typeof arg !== 'string') {
67
- return toJson(arg);
68
- }
69
- return arg;
52
+ return toDebugString(templateArgs[index + 2]);
7053 }
7154 return match;
7255 });
7356
74
- message = message + '\nhttp://errors.angularjs.org/1.3.0/' +
57
+ message = message + '\nhttp://errors.angularjs.org/1.3.6/' +
7558 (module ? module + '/' : '') + code;
7659 for (i = 2; i < arguments.length; i++) {
77
- message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
78
- encodeURIComponent(stringify(arguments[i]));
60
+ message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
61
+ encodeURIComponent(toDebugString(arguments[i]));
7962 }
8063 return new ErrorConstructor(message);
8164 };
....@@ -130,12 +113,11 @@
130113 isBoolean: true,
131114 isPromiseLike: true,
132115 trim: true,
116
+ escapeForRegexp: true,
133117 isElement: true,
134118 makeMap: true,
135
- size: true,
136119 includes: true,
137120 arrayRemove: true,
138
- isLeafNode: true,
139121 copy: true,
140122 shallowCopy: true,
141123 equals: true,
....@@ -205,7 +187,7 @@
205187 * @param {string} string String to be converted to lowercase.
206188 * @returns {string} Lowercased string.
207189 */
208
-var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
190
+var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
209191 var hasOwnProperty = Object.prototype.hasOwnProperty;
210192
211193 /**
....@@ -218,7 +200,7 @@
218200 * @param {string} string String to be converted to uppercase.
219201 * @returns {string} Uppercased string.
220202 */
221
-var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};
203
+var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
222204
223205
224206 var manualLowercase = function(s) {
....@@ -244,8 +226,8 @@
244226 }
245227
246228
247
-var /** holds major version number for IE or NaN for real browsers */
248
- msie,
229
+var
230
+ msie, // holds major version number for IE, or NaN if UA is not IE.
249231 jqLite, // delay binding since jQuery could be loaded after us.
250232 jQuery, // delay binding
251233 slice = [].slice,
....@@ -302,6 +284,11 @@
302284 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
303285 * using the `hasOwnProperty` method.
304286 *
287
+ * Unlike ES262's
288
+ * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
289
+ * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
290
+ * return the value provided.
291
+ *
305292 ```js
306293 var values = {name: 'misko', gender: 'male'};
307294 var log = [];
....@@ -349,18 +336,12 @@
349336 }
350337
351338 function sortedKeys(obj) {
352
- var keys = [];
353
- for (var key in obj) {
354
- if (obj.hasOwnProperty(key)) {
355
- keys.push(key);
356
- }
357
- }
358
- return keys.sort();
339
+ return Object.keys(obj).sort();
359340 }
360341
361342 function forEachSorted(obj, iterator, context) {
362343 var keys = sortedKeys(obj);
363
- for ( var i = 0; i < keys.length; i++) {
344
+ for (var i = 0; i < keys.length; i++) {
364345 iterator.call(context, obj[keys[i]], keys[i]);
365346 }
366347 return keys;
....@@ -415,6 +396,7 @@
415396 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
416397 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
417398 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
399
+ * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy).
418400 *
419401 * @param {Object} dst Destination object.
420402 * @param {...Object} src Source object(s).
....@@ -444,7 +426,7 @@
444426
445427
446428 function inherit(parent, extra) {
447
- return extend(new (extend(function() {}, {prototype:parent}))(), extra);
429
+ return extend(Object.create(parent), extra);
448430 }
449431
450432 /**
....@@ -501,7 +483,7 @@
501483 * @param {*} value Reference to check.
502484 * @returns {boolean} True if `value` is undefined.
503485 */
504
-function isUndefined(value){return typeof value === 'undefined';}
486
+function isUndefined(value) {return typeof value === 'undefined';}
505487
506488
507489 /**
....@@ -516,7 +498,7 @@
516498 * @param {*} value Reference to check.
517499 * @returns {boolean} True if `value` is defined.
518500 */
519
-function isDefined(value){return typeof value !== 'undefined';}
501
+function isDefined(value) {return typeof value !== 'undefined';}
520502
521503
522504 /**
....@@ -532,7 +514,7 @@
532514 * @param {*} value Reference to check.
533515 * @returns {boolean} True if `value` is an `Object` but not `null`.
534516 */
535
-function isObject(value){
517
+function isObject(value) {
536518 // http://jsperf.com/isobject4
537519 return value !== null && typeof value === 'object';
538520 }
....@@ -550,7 +532,7 @@
550532 * @param {*} value Reference to check.
551533 * @returns {boolean} True if `value` is a `String`.
552534 */
553
-function isString(value){return typeof value === 'string';}
535
+function isString(value) {return typeof value === 'string';}
554536
555537
556538 /**
....@@ -565,7 +547,7 @@
565547 * @param {*} value Reference to check.
566548 * @returns {boolean} True if `value` is a `Number`.
567549 */
568
-function isNumber(value){return typeof value === 'number';}
550
+function isNumber(value) {return typeof value === 'number';}
569551
570552
571553 /**
....@@ -611,7 +593,7 @@
611593 * @param {*} value Reference to check.
612594 * @returns {boolean} True if `value` is a `Function`.
613595 */
614
-function isFunction(value){return typeof value === 'function';}
596
+function isFunction(value) {return typeof value === 'function';}
615597
616598
617599 /**
....@@ -667,6 +649,14 @@
667649 return isString(value) ? value.trim() : value;
668650 };
669651
652
+// Copied from:
653
+// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
654
+// Prereq: s is a string.
655
+var escapeForRegexp = function(s) {
656
+ return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
657
+ replace(/\x08/g, '\\x08');
658
+};
659
+
670660
671661 /**
672662 * @ngdoc function
....@@ -692,43 +682,15 @@
692682 */
693683 function makeMap(str) {
694684 var obj = {}, items = str.split(","), i;
695
- for ( i = 0; i < items.length; i++ )
685
+ for (i = 0; i < items.length; i++)
696686 obj[ items[i] ] = true;
697687 return obj;
698688 }
699689
700690
701691 function nodeName_(element) {
702
- return lowercase(element.nodeName || element[0].nodeName);
692
+ return lowercase(element.nodeName || (element[0] && element[0].nodeName));
703693 }
704
-
705
-
706
-/**
707
- * @description
708
- * Determines the number of elements in an array, the number of properties an object has, or
709
- * the length of a string.
710
- *
711
- * Note: This function is used to augment the Object type in Angular expressions. See
712
- * {@link angular.Object} for more information about Angular arrays.
713
- *
714
- * @param {Object|Array|string} obj Object, array, or string to inspect.
715
- * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object
716
- * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array.
717
- */
718
-function size(obj, ownPropsOnly) {
719
- var count = 0, key;
720
-
721
- if (isArray(obj) || isString(obj)) {
722
- return obj.length;
723
- } else if (isObject(obj)) {
724
- for (key in obj)
725
- if (!ownPropsOnly || obj.hasOwnProperty(key))
726
- count++;
727
- }
728
-
729
- return count;
730
-}
731
-
732694
733695 function includes(array, obj) {
734696 return Array.prototype.indexOf.call(array, obj) != -1;
....@@ -736,21 +698,9 @@
736698
737699 function arrayRemove(array, value) {
738700 var index = array.indexOf(value);
739
- if (index >=0)
701
+ if (index >= 0)
740702 array.splice(index, 1);
741703 return value;
742
-}
743
-
744
-function isLeafNode (node) {
745
- if (node) {
746
- switch (nodeName_(node)) {
747
- case "option":
748
- case "pre":
749
- case "title":
750
- return true;
751
- }
752
- }
753
- return false;
754704 }
755705
756706 /**
....@@ -850,7 +800,7 @@
850800 var result;
851801 if (isArray(source)) {
852802 destination.length = 0;
853
- for ( var i = 0; i < source.length; i++) {
803
+ for (var i = 0; i < source.length; i++) {
854804 result = copy(source[i], null, stackSource, stackDest);
855805 if (isObject(source[i])) {
856806 stackSource.push(source[i]);
....@@ -867,8 +817,8 @@
867817 delete destination[key];
868818 });
869819 }
870
- for ( var key in source) {
871
- if(source.hasOwnProperty(key)) {
820
+ for (var key in source) {
821
+ if (source.hasOwnProperty(key)) {
872822 result = copy(source[key], null, stackSource, stackDest);
873823 if (isObject(source[key])) {
874824 stackSource.push(source[key]);
....@@ -949,7 +899,7 @@
949899 if (isArray(o1)) {
950900 if (!isArray(o2)) return false;
951901 if ((length = o1.length) == o2.length) {
952
- for(key=0; key<length; key++) {
902
+ for (key = 0; key < length; key++) {
953903 if (!equals(o1[key], o2[key])) return false;
954904 }
955905 return true;
....@@ -962,12 +912,12 @@
962912 } else {
963913 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
964914 keySet = {};
965
- for(key in o1) {
915
+ for (key in o1) {
966916 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
967917 if (!equals(o1[key], o2[key])) return false;
968918 keySet[key] = true;
969919 }
970
- for(key in o2) {
920
+ for (key in o2) {
971921 if (!keySet.hasOwnProperty(key) &&
972922 key.charAt(0) !== '$' &&
973923 o2[key] !== undefined &&
....@@ -1035,7 +985,7 @@
1035985 return curryArgs.length
1036986 ? function() {
1037987 return arguments.length
1038
- ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
988
+ ? fn.apply(self, concat(curryArgs, arguments, 0))
1039989 : fn.apply(self, curryArgs);
1040990 }
1041991 : function() {
....@@ -1078,12 +1028,16 @@
10781028 * stripped since angular uses this notation internally.
10791029 *
10801030 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1081
- * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
1031
+ * @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
1032
+ * If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
10821033 * @returns {string|undefined} JSON-ified string representing `obj`.
10831034 */
10841035 function toJson(obj, pretty) {
10851036 if (typeof obj === 'undefined') return undefined;
1086
- return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
1037
+ if (!isNumber(pretty)) {
1038
+ pretty = pretty ? 2 : null;
1039
+ }
1040
+ return JSON.stringify(obj, toJsonReplacer, pretty);
10871041 }
10881042
10891043
....@@ -1115,14 +1069,14 @@
11151069 // turns out IE does not let you set .html() on elements which
11161070 // are not allowed to have children. So we just ignore it.
11171071 element.empty();
1118
- } catch(e) {}
1072
+ } catch (e) {}
11191073 var elemHtml = jqLite('<div>').append(element).html();
11201074 try {
11211075 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
11221076 elemHtml.
11231077 match(/^(<[^>]+>)/)[1].
11241078 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1125
- } catch(e) {
1079
+ } catch (e) {
11261080 return lowercase(elemHtml);
11271081 }
11281082
....@@ -1142,7 +1096,7 @@
11421096 function tryDecodeURIComponent(value) {
11431097 try {
11441098 return decodeURIComponent(value);
1145
- } catch(e) {
1099
+ } catch (e) {
11461100 // Ignore any invalid uri component
11471101 }
11481102 }
....@@ -1155,14 +1109,14 @@
11551109 function parseKeyValue(/**string*/keyValue) {
11561110 var obj = {}, key_value, key;
11571111 forEach((keyValue || "").split('&'), function(keyValue) {
1158
- if ( keyValue ) {
1112
+ if (keyValue) {
11591113 key_value = keyValue.replace(/\+/g,'%20').split('=');
11601114 key = tryDecodeURIComponent(key_value[0]);
1161
- if ( isDefined(key) ) {
1115
+ if (isDefined(key)) {
11621116 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
11631117 if (!hasOwnProperty.call(obj, key)) {
11641118 obj[key] = val;
1165
- } else if(isArray(obj[key])) {
1119
+ } else if (isArray(obj[key])) {
11661120 obj[key].push(val);
11671121 } else {
11681122 obj[key] = [obj[key],val];
....@@ -1235,7 +1189,7 @@
12351189 function getNgAttribute(element, ngAttr) {
12361190 var attr, i, ii = ngAttrPrefixes.length;
12371191 element = jqLite(element);
1238
- for (i=0; i<ii; ++i) {
1192
+ for (i = 0; i < ii; ++i) {
12391193 attr = ngAttrPrefixes[i] + ngAttr;
12401194 if (isString(attr = element.attr(attr))) {
12411195 return attr;
....@@ -1445,8 +1399,8 @@
14451399 * @param {Object=} config an object for defining configuration options for the application. The
14461400 * following keys are supported:
14471401 *
1448
- * - `strictDi`: disable automatic function annotation for the application. This is meant to
1449
- * assist in finding bugs which break minified code.
1402
+ * * `strictDi` - disable automatic function annotation for the application. This is meant to
1403
+ * assist in finding bugs which break minified code. Defaults to `false`.
14501404 *
14511405 * @returns {auto.$injector} Returns the newly created injector for this app.
14521406 */
....@@ -1999,7 +1953,7 @@
19991953 config(configFn);
20001954 }
20011955
2002
- return moduleInstance;
1956
+ return moduleInstance;
20031957
20041958 /**
20051959 * @param {string} provider
....@@ -2018,6 +1972,34 @@
20181972 };
20191973 });
20201974
1975
+}
1976
+
1977
+/* global: toDebugString: true */
1978
+
1979
+function serializeObject(obj) {
1980
+ var seen = [];
1981
+
1982
+ return JSON.stringify(obj, function(key, val) {
1983
+ val = toJsonReplacer(key, val);
1984
+ if (isObject(val)) {
1985
+
1986
+ if (seen.indexOf(val) >= 0) return '<<already seen>>';
1987
+
1988
+ seen.push(val);
1989
+ }
1990
+ return val;
1991
+ });
1992
+}
1993
+
1994
+function toDebugString(obj) {
1995
+ if (typeof obj === 'function') {
1996
+ return obj.toString().replace(/ \{[\s\S]*$/, '');
1997
+ } else if (typeof obj === 'undefined') {
1998
+ return 'undefined';
1999
+ } else if (typeof obj !== 'string') {
2000
+ return serializeObject(obj);
2001
+ }
2002
+ return obj;
20212003 }
20222004
20232005 /* global angularModule: true,
....@@ -2103,7 +2085,8 @@
21032085 $TimeoutProvider,
21042086 $$RAFProvider,
21052087 $$AsyncCallbackProvider,
2106
- $WindowProvider
2088
+ $WindowProvider,
2089
+ $$jqLiteProvider
21072090 */
21082091
21092092
....@@ -2122,15 +2105,15 @@
21222105 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
21232106 */
21242107 var version = {
2125
- full: '1.3.0', // all of these placeholder strings will be replaced by grunt's
2108
+ full: '1.3.6', // all of these placeholder strings will be replaced by grunt's
21262109 major: 1, // package task
21272110 minor: 3,
2128
- dot: 0,
2129
- codeName: 'superluminal-nudge'
2111
+ dot: 6,
2112
+ codeName: 'robofunky-danceblaster'
21302113 };
21312114
21322115
2133
-function publishExternalAPI(angular){
2116
+function publishExternalAPI(angular) {
21342117 extend(angular, {
21352118 'bootstrap': bootstrap,
21362119 'copy': copy,
....@@ -2256,7 +2239,8 @@
22562239 $timeout: $TimeoutProvider,
22572240 $window: $WindowProvider,
22582241 $$rAF: $$RAFProvider,
2259
- $$asyncCallback : $$AsyncCallbackProvider
2242
+ $$asyncCallback: $$AsyncCallbackProvider,
2243
+ $$jqLite: $$jqLiteProvider
22602244 });
22612245 }
22622246 ]);
....@@ -2306,7 +2290,7 @@
23062290 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
23072291 * - [`clone()`](http://api.jquery.com/clone/)
23082292 * - [`contents()`](http://api.jquery.com/contents/)
2309
- * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
2293
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`
23102294 * - [`data()`](http://api.jquery.com/data/)
23112295 * - [`detach()`](http://api.jquery.com/detach/)
23122296 * - [`empty()`](http://api.jquery.com/empty/)
....@@ -2349,10 +2333,12 @@
23492333 * `'ngModel'`).
23502334 * - `injector()` - retrieves the injector of the current element or its parent.
23512335 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2352
- * element or its parent.
2336
+ * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2337
+ * be enabled.
23532338 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
23542339 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
23552340 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2341
+ * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
23562342 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
23572343 * parent element is reached.
23582344 *
....@@ -2384,7 +2370,7 @@
23842370
23852371 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
23862372 var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2387
-var MOUSE_EVENT_MAP= { mouseleave : "mouseout", mouseenter : "mouseover"};
2373
+var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
23882374 var jqLiteMinErr = minErr('jqLite');
23892375
23902376 /**
....@@ -2513,7 +2499,7 @@
25132499 return element.cloneNode(true);
25142500 }
25152501
2516
-function jqLiteDealoc(element, onlyDescendants){
2502
+function jqLiteDealoc(element, onlyDescendants) {
25172503 if (!onlyDescendants) jqLiteRemoveData(element);
25182504
25192505 if (element.querySelectorAll) {
....@@ -2620,7 +2606,7 @@
26202606 function jqLiteHasClass(element, selector) {
26212607 if (!element.getAttribute) return false;
26222608 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
2623
- indexOf( " " + selector + " " ) > -1);
2609
+ indexOf(" " + selector + " ") > -1);
26242610 }
26252611
26262612 function jqLiteRemoveClass(element, cssClasses) {
....@@ -2679,13 +2665,13 @@
26792665
26802666
26812667 function jqLiteController(element, name) {
2682
- return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
2668
+ return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
26832669 }
26842670
26852671 function jqLiteInheritedData(element, name, value) {
26862672 // if element is the document object work with the html element instead
26872673 // this makes $(document).scope() possible
2688
- if(element.nodeType == NODE_TYPE_DOCUMENT) {
2674
+ if (element.nodeType == NODE_TYPE_DOCUMENT) {
26892675 element = element.documentElement;
26902676 }
26912677 var names = isArray(name) ? name : [name];
....@@ -2743,7 +2729,7 @@
27432729 }
27442730
27452731 // check if document is already loaded
2746
- if (document.readyState === 'complete'){
2732
+ if (document.readyState === 'complete') {
27472733 setTimeout(trigger);
27482734 } else {
27492735 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
....@@ -2751,12 +2737,11 @@
27512737 // jshint -W064
27522738 JQLite(window).on('load', trigger); // fallback to window.onload for others
27532739 // jshint +W064
2754
- this.on('DOMContentLoaded', trigger);
27552740 }
27562741 },
27572742 toString: function() {
27582743 var value = [];
2759
- forEach(this, function(e){ value.push('' + e);});
2744
+ forEach(this, function(e) { value.push('' + e);});
27602745 return '[' + value.join(', ') + ']';
27612746 },
27622747
....@@ -2784,11 +2769,11 @@
27842769 BOOLEAN_ELEMENTS[value] = true;
27852770 });
27862771 var ALIASED_ATTR = {
2787
- 'ngMinlength' : 'minlength',
2788
- 'ngMaxlength' : 'maxlength',
2789
- 'ngMin' : 'min',
2790
- 'ngMax' : 'max',
2791
- 'ngPattern' : 'pattern'
2772
+ 'ngMinlength': 'minlength',
2773
+ 'ngMaxlength': 'maxlength',
2774
+ 'ngMin': 'min',
2775
+ 'ngMax': 'max',
2776
+ 'ngPattern': 'pattern'
27922777 };
27932778
27942779 function getBooleanAttrName(element, name) {
....@@ -2847,7 +2832,7 @@
28472832 }
28482833 },
28492834
2850
- attr: function(element, name, value){
2835
+ attr: function(element, name, value) {
28512836 var lowercasedName = lowercase(name);
28522837 if (BOOLEAN_ATTR[lowercasedName]) {
28532838 if (isDefined(value)) {
....@@ -2860,7 +2845,7 @@
28602845 }
28612846 } else {
28622847 return (element[name] ||
2863
- (element.attributes.getNamedItem(name)|| noop).specified)
2848
+ (element.attributes.getNamedItem(name) || noop).specified)
28642849 ? lowercasedName
28652850 : undefined;
28662851 }
....@@ -2900,7 +2885,7 @@
29002885 if (isUndefined(value)) {
29012886 if (element.multiple && nodeName_(element) === 'select') {
29022887 var result = [];
2903
- forEach(element.options, function (option) {
2888
+ forEach(element.options, function(option) {
29042889 if (option.selected) {
29052890 result.push(option.value || option.text);
29062891 }
....@@ -2921,7 +2906,7 @@
29212906 },
29222907
29232908 empty: jqLiteEmpty
2924
-}, function(fn, name){
2909
+}, function(fn, name) {
29252910 /**
29262911 * Properties: writes return selection, reads return first value
29272912 */
....@@ -2973,7 +2958,7 @@
29732958 });
29742959
29752960 function createEventHandler(element, events) {
2976
- var eventHandler = function (event, type) {
2961
+ var eventHandler = function(event, type) {
29772962 // jQuery specific api
29782963 event.isDefaultPrevented = function() {
29792964 return event.defaultPrevented;
....@@ -3029,7 +3014,7 @@
30293014 forEach({
30303015 removeData: jqLiteRemoveData,
30313016
3032
- on: function jqLiteOn(element, type, fn, unsupported){
3017
+ on: function jqLiteOn(element, type, fn, unsupported) {
30333018 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
30343019
30353020 // Do not add event handlers to non-elements because they will not be cleaned up.
....@@ -3065,7 +3050,7 @@
30653050 var target = this, related = event.relatedTarget;
30663051 // For mousenter/leave call the handler if related is outside the target.
30673052 // NB: No relatedTarget if the mouse left/entered the browser window
3068
- if ( !related || (related !== target && !target.contains(related)) ){
3053
+ if (!related || (related !== target && !target.contains(related))) {
30693054 handle(event, type);
30703055 }
30713056 });
....@@ -3099,7 +3084,7 @@
30993084 replaceWith: function(element, replaceNode) {
31003085 var index, parent = element.parentNode;
31013086 jqLiteDealoc(element);
3102
- forEach(new JQLite(replaceNode), function(node){
3087
+ forEach(new JQLite(replaceNode), function(node) {
31033088 if (index) {
31043089 parent.insertBefore(node, index.nextSibling);
31053090 } else {
....@@ -3111,7 +3096,7 @@
31113096
31123097 children: function(element) {
31133098 var children = [];
3114
- forEach(element.childNodes, function(element){
3099
+ forEach(element.childNodes, function(element) {
31153100 if (element.nodeType === NODE_TYPE_ELEMENT)
31163101 children.push(element);
31173102 });
....@@ -3137,7 +3122,7 @@
31373122 prepend: function(element, node) {
31383123 if (element.nodeType === NODE_TYPE_ELEMENT) {
31393124 var index = element.firstChild;
3140
- forEach(new JQLite(node), function(child){
3125
+ forEach(new JQLite(node), function(child) {
31413126 element.insertBefore(child, index);
31423127 });
31433128 }
....@@ -3174,7 +3159,7 @@
31743159
31753160 toggleClass: function(element, selector, condition) {
31763161 if (selector) {
3177
- forEach(selector.split(' '), function(className){
3162
+ forEach(selector.split(' '), function(className) {
31783163 var classCondition = condition;
31793164 if (isUndefined(classCondition)) {
31803165 classCondition = !jqLiteHasClass(element, className);
....@@ -3239,14 +3224,14 @@
32393224 });
32403225 }
32413226 }
3242
-}, function(fn, name){
3227
+}, function(fn, name) {
32433228 /**
32443229 * chaining functions
32453230 */
32463231 JQLite.prototype[name] = function(arg1, arg2, arg3) {
32473232 var value;
32483233
3249
- for(var i = 0, ii = this.length; i < ii; i++) {
3234
+ for (var i = 0, ii = this.length; i < ii; i++) {
32503235 if (isUndefined(value)) {
32513236 value = fn(this[i], arg1, arg2, arg3);
32523237 if (isDefined(value)) {
....@@ -3264,6 +3249,27 @@
32643249 JQLite.prototype.bind = JQLite.prototype.on;
32653250 JQLite.prototype.unbind = JQLite.prototype.off;
32663251 });
3252
+
3253
+
3254
+// Provider for private $$jqLite service
3255
+function $$jqLiteProvider() {
3256
+ this.$get = function $$jqLite() {
3257
+ return extend(JQLite, {
3258
+ hasClass: function(node, classes) {
3259
+ if (node.attr) node = node[0];
3260
+ return jqLiteHasClass(node, classes);
3261
+ },
3262
+ addClass: function(node, classes) {
3263
+ if (node.attr) node = node[0];
3264
+ return jqLiteAddClass(node, classes);
3265
+ },
3266
+ removeClass: function(node, classes) {
3267
+ if (node.attr) node = node[0];
3268
+ return jqLiteRemoveClass(node, classes);
3269
+ }
3270
+ });
3271
+ };
3272
+}
32673273
32683274 /**
32693275 * Computes a hash of an 'obj'.
....@@ -3348,9 +3354,10 @@
33483354 * Creates an injector object that can be used for retrieving services as well as for
33493355 * dependency injection (see {@link guide/di dependency injection}).
33503356 *
3351
-
33523357 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3353
- * {@link angular.module}. The `ng` module must be explicitly added.
3358
+ * {@link angular.module}. The `ng` module must be explicitly added.
3359
+ * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3360
+ * disallows argument name annotation inference.
33543361 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
33553362 *
33563363 * @example
....@@ -3496,8 +3503,10 @@
34963503 * ## Inference
34973504 *
34983505 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3499
- * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with
3500
- * minification, and obfuscation tools since these tools change the argument names.
3506
+ * can then be parsed and the function arguments can be extracted. This method of discovering
3507
+ * annotations is disallowed when the injector is in strict mode.
3508
+ * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3509
+ * argument names.
35013510 *
35023511 * ## `$inject` Annotation
35033512 * By adding an `$inject` property onto a function the injection parameters can be specified.
....@@ -3514,6 +3523,7 @@
35143523 * Return an instance of the service.
35153524 *
35163525 * @param {string} name The name of the instance to retrieve.
3526
+ * @param {string} caller An optional string to provide the origin of the function call for error messages.
35173527 * @return {*} The instance.
35183528 */
35193529
....@@ -3582,6 +3592,8 @@
35823592 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
35833593 * ```
35843594 *
3595
+ * You can disallow this method by using strict injection mode.
3596
+ *
35853597 * This method does not work with code minification / obfuscation. For this reason the following
35863598 * annotation strategies are supported.
35873599 *
....@@ -3633,6 +3645,8 @@
36333645 *
36343646 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
36353647 * be retrieved as described above.
3648
+ *
3649
+ * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
36363650 *
36373651 * @returns {Array.<string>} The names of the services which the function requires.
36383652 */
....@@ -3960,14 +3974,17 @@
39603974 }
39613975 },
39623976 providerInjector = (providerCache.$injector =
3963
- createInternalInjector(providerCache, function() {
3977
+ createInternalInjector(providerCache, function(serviceName, caller) {
3978
+ if (angular.isString(caller)) {
3979
+ path.push(caller);
3980
+ }
39643981 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
39653982 })),
39663983 instanceCache = {},
39673984 instanceInjector = (instanceCache.$injector =
3968
- createInternalInjector(instanceCache, function(servicename) {
3969
- var provider = providerInjector.get(servicename + providerSuffix);
3970
- return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
3985
+ createInternalInjector(instanceCache, function(serviceName, caller) {
3986
+ var provider = providerInjector.get(serviceName + providerSuffix, caller);
3987
+ return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
39713988 }));
39723989
39733990
....@@ -4002,7 +4019,7 @@
40024019
40034020 function enforceReturnValue(name, factory) {
40044021 return function enforcedReturnValue() {
4005
- var result = instanceInjector.invoke(factory, this, undefined, name);
4022
+ var result = instanceInjector.invoke(factory, this);
40064023 if (isUndefined(result)) {
40074024 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
40084025 }
....@@ -4043,7 +4060,7 @@
40434060 ////////////////////////////////////
40444061 // Module Loading
40454062 ////////////////////////////////////
4046
- function loadModules(modulesToLoad){
4063
+ function loadModules(modulesToLoad) {
40474064 var runBlocks = [], moduleFn;
40484065 forEach(modulesToLoad, function(module) {
40494066 if (loadedModules.get(module)) return;
....@@ -4051,7 +4068,7 @@
40514068
40524069 function runInvokeQueue(queue) {
40534070 var i, ii;
4054
- for(i = 0, ii = queue.length; i < ii; i++) {
4071
+ for (i = 0, ii = queue.length; i < ii; i++) {
40554072 var invokeArgs = queue[i],
40564073 provider = providerInjector.get(invokeArgs[0]);
40574074
....@@ -4097,7 +4114,7 @@
40974114
40984115 function createInternalInjector(cache, factory) {
40994116
4100
- function getService(serviceName) {
4117
+ function getService(serviceName, caller) {
41014118 if (cache.hasOwnProperty(serviceName)) {
41024119 if (cache[serviceName] === INSTANTIATING) {
41034120 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
....@@ -4108,7 +4125,7 @@
41084125 try {
41094126 path.unshift(serviceName);
41104127 cache[serviceName] = INSTANTIATING;
4111
- return cache[serviceName] = factory(serviceName);
4128
+ return cache[serviceName] = factory(serviceName, caller);
41124129 } catch (err) {
41134130 if (cache[serviceName] === INSTANTIATING) {
41144131 delete cache[serviceName];
....@@ -4131,7 +4148,7 @@
41314148 length, i,
41324149 key;
41334150
4134
- for(i = 0, length = $inject.length; i < length; i++) {
4151
+ for (i = 0, length = $inject.length; i < length; i++) {
41354152 key = $inject[i];
41364153 if (typeof key !== 'string') {
41374154 throw $injectorMinErr('itkn',
....@@ -4140,7 +4157,7 @@
41404157 args.push(
41414158 locals && locals.hasOwnProperty(key)
41424159 ? locals[key]
4143
- : getService(key)
4160
+ : getService(key, serviceName)
41444161 );
41454162 }
41464163 if (isArray(fn)) {
....@@ -4153,14 +4170,11 @@
41534170 }
41544171
41554172 function instantiate(Type, locals, serviceName) {
4156
- var Constructor = function() {},
4157
- instance, returnedValue;
4158
-
41594173 // Check if Type is annotated and use just the given function at n-1 as parameter
41604174 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4161
- Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
4162
- instance = new Constructor();
4163
- returnedValue = invoke(Type, instance, locals, serviceName);
4175
+ // Object creation: http://jsperf.com/create-constructor/2
4176
+ var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
4177
+ var returnedValue = invoke(Type, instance, locals, serviceName);
41644178
41654179 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
41664180 }
....@@ -4196,7 +4210,7 @@
41964210 * @name $anchorScrollProvider#disableAutoScrolling
41974211 *
41984212 * @description
4199
- * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to
4213
+ * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
42004214 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
42014215 * Use this method to disable automatic scrolling.
42024216 *
....@@ -4347,7 +4361,6 @@
43474361 */
43484362 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
43494363 var document = $window.document;
4350
- var scrollScheduled = false;
43514364
43524365 // Helper function to get first anchor from a NodeList
43534366 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
....@@ -4521,7 +4534,7 @@
45214534 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
45224535 */
45234536 this.classNameFilter = function(expression) {
4524
- if(arguments.length === 1) {
4537
+ if (arguments.length === 1) {
45254538 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
45264539 }
45274540 return this.$$classNameFilter;
....@@ -4616,7 +4629,7 @@
46164629 * page}.
46174630 */
46184631 return {
4619
- animate : function(element, from, to) {
4632
+ animate: function(element, from, to) {
46204633 applyStyles(element, { from: from, to: to });
46214634 return asyncPromise();
46224635 },
....@@ -4637,7 +4650,7 @@
46374650 * @param {object=} options an optional collection of styles that will be applied to the element.
46384651 * @return {Promise} the animation callback promise
46394652 */
4640
- enter : function(element, parent, after, options) {
4653
+ enter: function(element, parent, after, options) {
46414654 applyStyles(element, options);
46424655 after ? after.after(element)
46434656 : parent.prepend(element);
....@@ -4655,7 +4668,7 @@
46554668 * @param {object=} options an optional collection of options that will be applied to the element.
46564669 * @return {Promise} the animation callback promise
46574670 */
4658
- leave : function(element, options) {
4671
+ leave: function(element, options) {
46594672 element.remove();
46604673 return asyncPromise();
46614674 },
....@@ -4678,7 +4691,7 @@
46784691 * @param {object=} options an optional collection of options that will be applied to the element.
46794692 * @return {Promise} the animation callback promise
46804693 */
4681
- move : function(element, parent, after, options) {
4694
+ move: function(element, parent, after, options) {
46824695 // Do not remove element before insert. Removing will cause data associated with the
46834696 // element to be dropped. Insert will implicitly do the remove.
46844697 return this.enter(element, parent, after, options);
....@@ -4697,16 +4710,16 @@
46974710 * @param {object=} options an optional collection of options that will be applied to the element.
46984711 * @return {Promise} the animation callback promise
46994712 */
4700
- addClass : function(element, className, options) {
4713
+ addClass: function(element, className, options) {
47014714 return this.setClass(element, className, [], options);
47024715 },
47034716
4704
- $$addClassImmediately : function(element, className, options) {
4717
+ $$addClassImmediately: function(element, className, options) {
47054718 element = jqLite(element);
47064719 className = !isString(className)
47074720 ? (isArray(className) ? className.join(' ') : '')
47084721 : className;
4709
- forEach(element, function (element) {
4722
+ forEach(element, function(element) {
47104723 jqLiteAddClass(element, className);
47114724 });
47124725 applyStyles(element, options);
....@@ -4726,16 +4739,16 @@
47264739 * @param {object=} options an optional collection of options that will be applied to the element.
47274740 * @return {Promise} the animation callback promise
47284741 */
4729
- removeClass : function(element, className, options) {
4742
+ removeClass: function(element, className, options) {
47304743 return this.setClass(element, [], className, options);
47314744 },
47324745
4733
- $$removeClassImmediately : function(element, className, options) {
4746
+ $$removeClassImmediately: function(element, className, options) {
47344747 element = jqLite(element);
47354748 className = !isString(className)
47364749 ? (isArray(className) ? className.join(' ') : '')
47374750 : className;
4738
- forEach(element, function (element) {
4751
+ forEach(element, function(element) {
47394752 jqLiteRemoveClass(element, className);
47404753 });
47414754 applyStyles(element, options);
....@@ -4756,7 +4769,7 @@
47564769 * @param {object=} options an optional collection of options that will be applied to the element.
47574770 * @return {Promise} the animation callback promise
47584771 */
4759
- setClass : function(element, add, remove, options) {
4772
+ setClass: function(element, add, remove, options) {
47604773 var self = this;
47614774 var STORAGE_KEY = '$$animateClasses';
47624775 var createdCache = false;
....@@ -4766,7 +4779,7 @@
47664779 if (!cache) {
47674780 cache = {
47684781 classes: {},
4769
- options : options
4782
+ options: options
47704783 };
47714784 createdCache = true;
47724785 } else if (options && cache.options) {
....@@ -4803,20 +4816,20 @@
48034816 return cache.promise;
48044817 },
48054818
4806
- $$setClassImmediately : function(element, add, remove, options) {
4819
+ $$setClassImmediately: function(element, add, remove, options) {
48074820 add && this.$$addClassImmediately(element, add);
48084821 remove && this.$$removeClassImmediately(element, remove);
48094822 applyStyles(element, options);
48104823 return asyncPromise();
48114824 },
48124825
4813
- enabled : noop,
4814
- cancel : noop
4826
+ enabled: noop,
4827
+ cancel: noop
48154828 };
48164829 }];
48174830 }];
48184831
4819
-function $$AsyncCallbackProvider(){
4832
+function $$AsyncCallbackProvider() {
48204833 this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) {
48214834 return $$rAF.supported
48224835 ? function(fn) { return $$rAF(fn); }
....@@ -4846,8 +4859,7 @@
48464859 /**
48474860 * @param {object} window The global window object.
48484861 * @param {object} document jQuery wrapped document.
4849
- * @param {function()} XHR XMLHttpRequest constructor.
4850
- * @param {object} $log console.log or an object with the same interface.
4862
+ * @param {object} $log window.console or an object with the same interface.
48514863 * @param {object} $sniffer $sniffer service
48524864 */
48534865 function Browser(window, document, $log, $sniffer) {
....@@ -4878,7 +4890,7 @@
48784890 } finally {
48794891 outstandingRequestCount--;
48804892 if (outstandingRequestCount === 0) {
4881
- while(outstandingRequestCallbacks.length) {
4893
+ while (outstandingRequestCallbacks.length) {
48824894 try {
48834895 outstandingRequestCallbacks.pop()();
48844896 } catch (e) {
....@@ -4887,6 +4899,11 @@
48874899 }
48884900 }
48894901 }
4902
+ }
4903
+
4904
+ function getHash(url) {
4905
+ var index = url.indexOf('#');
4906
+ return index === -1 ? '' : url.substr(index + 1);
48904907 }
48914908
48924909 /**
....@@ -4899,7 +4916,7 @@
48994916 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire
49004917 // at some deterministic time in respect to the test runner's actions. Leaving things up to the
49014918 // regular poller would result in flaky tests.
4902
- forEach(pollFns, function(pollFn){ pollFn(); });
4919
+ forEach(pollFns, function(pollFn) { pollFn(); });
49034920
49044921 if (outstandingRequestCount === 0) {
49054922 callback();
....@@ -4941,7 +4958,7 @@
49414958 */
49424959 function startPoller(interval, setTimeout) {
49434960 (function check() {
4944
- forEach(pollFns, function(pollFn){ pollFn(); });
4961
+ forEach(pollFns, function(pollFn) { pollFn(); });
49454962 pollTimeout = setTimeout(check, interval);
49464963 })();
49474964 }
....@@ -4998,7 +5015,7 @@
49985015 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
49995016 // See https://github.com/angular/angular.js/commit/ffb2701
50005017 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5001
- return;
5018
+ return self;
50025019 }
50035020 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
50045021 lastBrowserUrl = url;
....@@ -5018,8 +5035,10 @@
50185035 }
50195036 if (replace) {
50205037 location.replace(url);
5021
- } else {
5038
+ } else if (!sameBase) {
50225039 location.href = url;
5040
+ } else {
5041
+ location.hash = getHash(url);
50235042 }
50245043 }
50255044 return self;
....@@ -5197,8 +5216,8 @@
51975216 // - 20 cookies per unique domain
51985217 // - 4096 bytes per cookie
51995218 if (cookieLength > 4096) {
5200
- $log.warn("Cookie '"+ name +
5201
- "' possibly not set or overflowed because it was too large ("+
5219
+ $log.warn("Cookie '" + name +
5220
+ "' possibly not set or overflowed because it was too large (" +
52025221 cookieLength + " > 4096 bytes)!");
52035222 }
52045223 }
....@@ -5276,9 +5295,9 @@
52765295
52775296 }
52785297
5279
-function $BrowserProvider(){
5298
+function $BrowserProvider() {
52805299 this.$get = ['$window', '$log', '$sniffer', '$document',
5281
- function( $window, $log, $sniffer, $document){
5300
+ function($window, $log, $sniffer, $document) {
52825301 return new Browser($window, $document, $log, $sniffer);
52835302 }];
52845303 }
....@@ -5652,7 +5671,8 @@
56525671 * ```
56535672 *
56545673 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
5655
- * the document, but it must be below the `ng-app` definition.
5674
+ * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
5675
+ * element with ng-app attribute), otherwise the template will be ignored.
56565676 *
56575677 * Adding via the $templateCache service:
56585678 *
....@@ -5797,7 +5817,7 @@
57975817 * #### `multiElement`
57985818 * When this property is set to true, the HTML compiler will collect DOM nodes between
57995819 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
5800
- * together as the directive elements. It is recomended that this feature be used on directives
5820
+ * together as the directive elements. It is recommended that this feature be used on directives
58015821 * which are not strictly behavioural (such as {@link ngClick}), and which
58025822 * do not manipulate or replace child nodes (such as {@link ngInclude}).
58035823 *
....@@ -5846,7 +5866,9 @@
58465866 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
58475867 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
58485868 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
5849
- * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional.
5869
+ * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
5870
+ * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
5871
+ * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
58505872 *
58515873 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
58525874 * If no `attr` name is specified then the attribute name is assumed to be the same as the
....@@ -5860,7 +5882,7 @@
58605882 *
58615883 *
58625884 * #### `bindToController`
5863
- * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will
5885
+ * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
58645886 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
58655887 * is instantiated, the initial values of the isolate scope bindings are already available.
58665888 *
....@@ -6302,10 +6324,17 @@
63026324 *
63036325 *
63046326 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
6305
- * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives.
6327
+ * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
6328
+ *
6329
+ * <div class="alert alert-error">
6330
+ * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
6331
+ * e.g. will not use the right outer scope. Please pass the transclude function as a
6332
+ * `parentBoundTranscludeFn` to the link function instead.
6333
+ * </div>
6334
+ *
63066335 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
63076336 * root element(s), not their children)
6308
- * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template
6337
+ * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
63096338 * (a DOM element/tree) to a scope. Where:
63106339 *
63116340 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
....@@ -6316,6 +6345,19 @@
63166345 *
63176346 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
63186347 * * `scope` - is the current scope with which the linking function is working with.
6348
+ *
6349
+ * * `options` - An optional object hash with linking options. If `options` is provided, then the following
6350
+ * keys may be used to control linking behavior:
6351
+ *
6352
+ * * `parentBoundTranscludeFn` - the transclude function made available to
6353
+ * directives; if given, it will be passed through to the link functions of
6354
+ * directives found in `element` during compilation.
6355
+ * * `transcludeControllers` - an object hash with keys that map controller names
6356
+ * to controller instances; if given, it will make the controllers
6357
+ * available to directives.
6358
+ * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
6359
+ * the cloned elements; only needed for transcludes that are allowed to contain non html
6360
+ * elements (e.g. SVG elements). See also the directive.controller property.
63196361 *
63206362 * Calling the linking function returns the element of the template. It is either the original
63216363 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
....@@ -6362,8 +6404,8 @@
63626404 function $CompileProvider($provide, $$sanitizeUriProvider) {
63636405 var hasDirectives = {},
63646406 Suffix = 'Directive',
6365
- COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
6366
- CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
6407
+ COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
6408
+ CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
63676409 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
63686410 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
63696411
....@@ -6373,7 +6415,7 @@
63736415 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
63746416
63756417 function parseIsolateBindings(scope, directiveName) {
6376
- var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
6418
+ var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
63776419
63786420 var bindings = {};
63796421
....@@ -6388,9 +6430,10 @@
63886430 }
63896431
63906432 bindings[scopeName] = {
6391
- attrName: match[3] || scopeName,
6392
- mode: match[1],
6393
- optional: match[2] === '?'
6433
+ mode: match[1][0],
6434
+ collection: match[2] === '*',
6435
+ optional: match[3] === '?',
6436
+ attrName: match[4] || scopeName
63946437 };
63956438 });
63966439
....@@ -6462,7 +6505,7 @@
64626505 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
64636506 * urls during a[href] sanitization.
64646507 *
6465
- * The sanitization is a security measure aimed at prevent XSS attacks via html links.
6508
+ * The sanitization is a security measure aimed at preventing XSS attacks via html links.
64666509 *
64676510 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
64686511 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
....@@ -6529,14 +6572,14 @@
65296572 * * `ng-binding` CSS class
65306573 * * `$binding` data property containing an array of the binding expressions
65316574 *
6532
- * You may want to use this in production for a significant performance boost. See
6575
+ * You may want to disable this in production for a significant performance boost. See
65336576 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
65346577 *
65356578 * The default value is true.
65366579 */
65376580 var debugInfoEnabled = true;
65386581 this.debugInfoEnabled = function(enabled) {
6539
- if(isDefined(enabled)) {
6582
+ if (isDefined(enabled)) {
65406583 debugInfoEnabled = enabled;
65416584 return this;
65426585 }
....@@ -6566,6 +6609,21 @@
65666609 };
65676610
65686611 Attributes.prototype = {
6612
+ /**
6613
+ * @ngdoc method
6614
+ * @name $compile.directive.Attributes#$normalize
6615
+ * @kind function
6616
+ *
6617
+ * @description
6618
+ * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
6619
+ * `data-`) to its normalized, camelCase form.
6620
+ *
6621
+ * Also there is special case for Moz prefix starting with upper case letter.
6622
+ *
6623
+ * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
6624
+ *
6625
+ * @param {string} name Name to normalize
6626
+ */
65696627 $normalize: directiveNormalize,
65706628
65716629
....@@ -6580,8 +6638,8 @@
65806638 *
65816639 * @param {string} classVal The className value that will be added to the element
65826640 */
6583
- $addClass : function(classVal) {
6584
- if(classVal && classVal.length > 0) {
6641
+ $addClass: function(classVal) {
6642
+ if (classVal && classVal.length > 0) {
65856643 $animate.addClass(this.$$element, classVal);
65866644 }
65876645 },
....@@ -6597,8 +6655,8 @@
65976655 *
65986656 * @param {string} classVal The className value that will be removed from the element
65996657 */
6600
- $removeClass : function(classVal) {
6601
- if(classVal && classVal.length > 0) {
6658
+ $removeClass: function(classVal) {
6659
+ if (classVal && classVal.length > 0) {
66026660 $animate.removeClass(this.$$element, classVal);
66036661 }
66046662 },
....@@ -6615,7 +6673,7 @@
66156673 * @param {string} newClasses The current CSS className value
66166674 * @param {string} oldClasses The former CSS className value
66176675 */
6618
- $updateClass : function(newClasses, oldClasses) {
6676
+ $updateClass: function(newClasses, oldClasses) {
66196677 var toAdd = tokenDifference(newClasses, oldClasses);
66206678 if (toAdd && toAdd.length) {
66216679 $animate.addClass(this.$$element, toAdd);
....@@ -6645,13 +6703,12 @@
66456703 booleanKey = getBooleanAttrName(node, key),
66466704 aliasedKey = getAliasedAttrName(node, key),
66476705 observer = key,
6648
- normalizedVal,
66496706 nodeName;
66506707
66516708 if (booleanKey) {
66526709 this.$$element.prop(key, value);
66536710 attrName = booleanKey;
6654
- } else if(aliasedKey) {
6711
+ } else if (aliasedKey) {
66556712 this[aliasedKey] = value;
66566713 observer = aliasedKey;
66576714 }
....@@ -6689,22 +6746,22 @@
66896746
66906747 // for each tuples
66916748 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
6692
- for (var i=0; i<nbrUrisWith2parts; i++) {
6693
- var innerIdx = i*2;
6749
+ for (var i = 0; i < nbrUrisWith2parts; i++) {
6750
+ var innerIdx = i * 2;
66946751 // sanitize the uri
6695
- result += $$sanitizeUri(trim( rawUris[innerIdx]), true);
6752
+ result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
66966753 // add the descriptor
6697
- result += ( " " + trim(rawUris[innerIdx+1]));
6754
+ result += (" " + trim(rawUris[innerIdx + 1]));
66986755 }
66996756
67006757 // split the last item into uri and descriptor
6701
- var lastTuple = trim(rawUris[i*2]).split(/\s/);
6758
+ var lastTuple = trim(rawUris[i * 2]).split(/\s/);
67026759
67036760 // sanitize the last uri
67046761 result += $$sanitizeUri(trim(lastTuple[0]), true);
67056762
67066763 // and add the last descriptor if any
6707
- if( lastTuple.length === 2) {
6764
+ if (lastTuple.length === 2) {
67086765 result += (" " + trim(lastTuple[1]));
67096766 }
67106767 this[key] = value = result;
....@@ -6755,7 +6812,7 @@
67556812
67566813 listeners.push(fn);
67576814 $rootScope.$evalAsync(function() {
6758
- if (!listeners.$$inter) {
6815
+ if (!listeners.$$inter && attrs.hasOwnProperty(key)) {
67596816 // no one registered attribute interpolation function, so lets call it manually
67606817 fn(attrs[key]);
67616818 }
....@@ -6771,7 +6828,7 @@
67716828 function safeAddClass($element, className) {
67726829 try {
67736830 $element.addClass(className);
6774
- } catch(e) {
6831
+ } catch (e) {
67756832 // ignore, since it means that we are trying to set class on
67766833 // SVG element, where class name is read-only.
67776834 }
....@@ -6825,7 +6882,7 @@
68256882 }
68266883 // We can not compile top level text elements since text nodes can be merged and we will
68276884 // not be able to attach scope data to them, so we will wrap them in <span>
6828
- forEach($compileNodes, function(node, index){
6885
+ forEach($compileNodes, function(node, index) {
68296886 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
68306887 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
68316888 }
....@@ -6835,8 +6892,22 @@
68356892 maxPriority, ignoreDirective, previousCompileContext);
68366893 compile.$$addScopeClass($compileNodes);
68376894 var namespace = null;
6838
- return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){
6895
+ return function publicLinkFn(scope, cloneConnectFn, options) {
68396896 assertArg(scope, 'scope');
6897
+
6898
+ options = options || {};
6899
+ var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
6900
+ transcludeControllers = options.transcludeControllers,
6901
+ futureParentElement = options.futureParentElement;
6902
+
6903
+ // When `parentBoundTranscludeFn` is passed, it is a
6904
+ // `controllersBoundTransclude` function (it was previously passed
6905
+ // as `transclude` to directive.link) so we must unwrap it to get
6906
+ // its `boundTranscludeFn`
6907
+ if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
6908
+ parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
6909
+ }
6910
+
68406911 if (!namespace) {
68416912 namespace = detectNamespaceForChildElements(futureParentElement);
68426913 }
....@@ -6878,7 +6949,7 @@
68786949 if (!node) {
68796950 return 'html';
68806951 } else {
6881
- return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html';
6952
+ return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
68826953 }
68836954 }
68846955
....@@ -6960,7 +7031,7 @@
69607031 stableNodeList = nodeList;
69617032 }
69627033
6963
- for(i = 0, ii = linkFns.length; i < ii;) {
7034
+ for (i = 0, ii = linkFns.length; i < ii;) {
69647035 node = stableNodeList[linkFns[i++]];
69657036 nodeLinkFn = linkFns[i++];
69667037 childLinkFn = linkFns[i++];
....@@ -6973,7 +7044,7 @@
69737044 childScope = scope;
69747045 }
69757046
6976
- if ( nodeLinkFn.transcludeOnThisElement ) {
7047
+ if (nodeLinkFn.transcludeOnThisElement) {
69777048 childBoundTranscludeFn = createBoundTranscludeFn(
69787049 scope, nodeLinkFn.transclude, parentBoundTranscludeFn,
69797050 nodeLinkFn.elementTranscludeOnThisElement);
....@@ -7006,7 +7077,11 @@
70067077 transcludedScope.$$transcluded = true;
70077078 }
70087079
7009
- return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
7080
+ return transcludeFn(transcludedScope, cloneFn, {
7081
+ parentBoundTranscludeFn: previousBoundTranscludeFn,
7082
+ transcludeControllers: controllers,
7083
+ futureParentElement: futureParentElement
7084
+ });
70107085 };
70117086
70127087 return boundTranscludeFn;
....@@ -7028,7 +7103,7 @@
70287103 match,
70297104 className;
70307105
7031
- switch(nodeType) {
7106
+ switch (nodeType) {
70327107 case NODE_TYPE_ELEMENT: /* Element */
70337108 // use the node name: <directive>
70347109 addDirective(directives,
....@@ -7120,7 +7195,6 @@
71207195 var nodes = [];
71217196 var depth = 0;
71227197 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7123
- var startNode = node;
71247198 do {
71257199 if (!node) {
71267200 throw $compileMinErr('uterdir',
....@@ -7204,7 +7278,7 @@
72047278 directiveValue;
72057279
72067280 // executes all directives on the current element
7207
- for(var i = 0, ii = directives.length; i < ii; i++) {
7281
+ for (var i = 0, ii = directives.length; i < ii; i++) {
72087282 directive = directives[i];
72097283 var attrStart = directive.$$start;
72107284 var attrEnd = directive.$$end;
....@@ -7448,7 +7522,7 @@
74487522 "Controller '{0}', required by directive '{1}', can't be found!",
74497523 require, directiveName);
74507524 }
7451
- return value;
7525
+ return value || null;
74527526 } else if (isArray(require)) {
74537527 value = [];
74547528 forEach(require, function(require) {
....@@ -7475,7 +7549,13 @@
74757549 isolateScope = scope.$new(true);
74767550 }
74777551
7478
- transcludeFn = boundTranscludeFn && controllersBoundTransclude;
7552
+ if (boundTranscludeFn) {
7553
+ // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
7554
+ // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
7555
+ transcludeFn = controllersBoundTransclude;
7556
+ transcludeFn.$$boundTransclude = boundTranscludeFn;
7557
+ }
7558
+
74797559 if (controllerDirectives) {
74807560 // TODO: merge `controllers` and `elementControllers` into single object.
74817561 controllers = {};
....@@ -7510,8 +7590,6 @@
75107590 }
75117591
75127592 if (newIsolateScopeDirective) {
7513
- var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
7514
-
75157593 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
75167594 templateDirective === newIsolateScopeDirective.$$originalDirective)));
75177595 compile.$$addScopeClass($element, true);
....@@ -7537,7 +7615,7 @@
75377615 isolateBindingContext[scopeName] = value;
75387616 });
75397617 attrs.$$observers[attrName].$$scope = scope;
7540
- if( attrs[attrName] ) {
7618
+ if (attrs[attrName]) {
75417619 // If the attribute has been provided then we trigger an interpolation to ensure
75427620 // the value is there for use in the link fn
75437621 isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope);
....@@ -7552,7 +7630,7 @@
75527630 if (parentGet.literal) {
75537631 compare = equals;
75547632 } else {
7555
- compare = function(a,b) { return a === b || (a !== a && b !== b); };
7633
+ compare = function(a, b) { return a === b || (a !== a && b !== b); };
75567634 }
75577635 parentSet = parentGet.assign || function() {
75587636 // reset the change, or we will throw this exception on every $digest
....@@ -7576,7 +7654,12 @@
75767654 return lastValue = parentValue;
75777655 };
75787656 parentValueWatch.$stateful = true;
7579
- var unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
7657
+ var unwatch;
7658
+ if (definition.collection) {
7659
+ unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
7660
+ } else {
7661
+ unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
7662
+ }
75807663 isolateScope.$on('$destroy', unwatch);
75817664 break;
75827665
....@@ -7597,7 +7680,7 @@
75977680 }
75987681
75997682 // PRELINKING
7600
- for(i = 0, ii = preLinkFns.length; i < ii; i++) {
7683
+ for (i = 0, ii = preLinkFns.length; i < ii; i++) {
76017684 linkFn = preLinkFns[i];
76027685 invokeLinkFn(linkFn,
76037686 linkFn.isolateScope ? isolateScope : scope,
....@@ -7618,7 +7701,7 @@
76187701 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
76197702
76207703 // POSTLINKING
7621
- for(i = postLinkFns.length - 1; i >= 0; i--) {
7704
+ for (i = postLinkFns.length - 1; i >= 0; i--) {
76227705 linkFn = postLinkFns[i];
76237706 invokeLinkFn(linkFn,
76247707 linkFn.isolateScope ? isolateScope : scope,
....@@ -7678,11 +7761,11 @@
76787761 if (name === ignoreDirective) return null;
76797762 var match = null;
76807763 if (hasDirectives.hasOwnProperty(name)) {
7681
- for(var directive, directives = $injector.get(name + Suffix),
7682
- i = 0, ii = directives.length; i<ii; i++) {
7764
+ for (var directive, directives = $injector.get(name + Suffix),
7765
+ i = 0, ii = directives.length; i < ii; i++) {
76837766 try {
76847767 directive = directives[i];
7685
- if ( (maxPriority === undefined || maxPriority > directive.priority) &&
7768
+ if ((maxPriority === undefined || maxPriority > directive.priority) &&
76867769 directive.restrict.indexOf(location) != -1) {
76877770 if (startAttrName) {
76887771 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
....@@ -7690,7 +7773,7 @@
76907773 tDirectives.push(directive);
76917774 match = directive;
76927775 }
7693
- } catch(e) { $exceptionHandler(e); }
7776
+ } catch (e) { $exceptionHandler(e); }
76947777 }
76957778 }
76967779 return match;
....@@ -7707,8 +7790,8 @@
77077790 */
77087791 function directiveIsMultiElement(name) {
77097792 if (hasDirectives.hasOwnProperty(name)) {
7710
- for(var directive, directives = $injector.get(name + Suffix),
7711
- i = 0, ii = directives.length; i<ii; i++) {
7793
+ for (var directive, directives = $injector.get(name + Suffix),
7794
+ i = 0, ii = directives.length; i < ii; i++) {
77127795 directive = directives[i];
77137796 if (directive.multiElement) {
77147797 return true;
....@@ -7824,7 +7907,7 @@
78247907 });
78257908 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
78267909
7827
- while(linkQueue.length) {
7910
+ while (linkQueue.length) {
78287911 var scope = linkQueue.shift(),
78297912 beforeTemplateLinkNode = linkQueue.shift(),
78307913 linkRootElement = linkQueue.shift(),
....@@ -7861,10 +7944,10 @@
78617944 var childBoundTranscludeFn = boundTranscludeFn;
78627945 if (scope.$$destroyed) return;
78637946 if (linkQueue) {
7864
- linkQueue.push(scope);
7865
- linkQueue.push(node);
7866
- linkQueue.push(rootElement);
7867
- linkQueue.push(childBoundTranscludeFn);
7947
+ linkQueue.push(scope,
7948
+ node,
7949
+ rootElement,
7950
+ childBoundTranscludeFn);
78687951 } else {
78697952 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
78707953 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
....@@ -7923,11 +8006,11 @@
79238006
79248007 function wrapTemplate(type, template) {
79258008 type = lowercase(type || 'html');
7926
- switch(type) {
8009
+ switch (type) {
79278010 case 'svg':
79288011 case 'math':
79298012 var wrapper = document.createElement('div');
7930
- wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>';
8013
+ wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
79318014 return wrapper.childNodes[0].childNodes;
79328015 default:
79338016 return template;
....@@ -8004,7 +8087,7 @@
80048087 //skip animations when the first digest occurs (when
80058088 //both the new and the old values are the same) since
80068089 //the CSS classes are the non-interpolated values
8007
- if(name === 'class' && newValue != oldValue) {
8090
+ if (name === 'class' && newValue != oldValue) {
80088091 attr.$updateClass(newValue, oldValue);
80098092 } else {
80108093 attr.$set(name, newValue);
....@@ -8034,7 +8117,7 @@
80348117 i, ii;
80358118
80368119 if ($rootElement) {
8037
- for(i = 0, ii = $rootElement.length; i < ii; i++) {
8120
+ for (i = 0, ii = $rootElement.length; i < ii; i++) {
80388121 if ($rootElement[i] == firstElementToRemove) {
80398122 $rootElement[i++] = newNode;
80408123 for (var j = i, j2 = j + removeCount - 1,
....@@ -8109,23 +8192,16 @@
81098192 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
81108193 try {
81118194 linkFn(scope, $element, attrs, controllers, transcludeFn);
8112
- } catch(e) {
8195
+ } catch (e) {
81138196 $exceptionHandler(e, startingTag($element));
81148197 }
81158198 }
81168199 }];
81178200 }
81188201
8119
-var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
8202
+var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
81208203 /**
81218204 * Converts all accepted directives format into proper directive name.
8122
- * All of these will become 'myDirective':
8123
- * my:Directive
8124
- * my-directive
8125
- * x-my-directive
8126
- * data-my:directive
8127
- *
8128
- * Also there is special case for Moz prefix starting with upper case letter.
81298205 * @param name Name to normalize
81308206 */
81318207 function directiveNormalize(name) {
....@@ -8182,7 +8258,7 @@
81828258 /* NodeList */ nodeList,
81838259 /* Element */ rootElement,
81848260 /* function(Function) */ boundTranscludeFn
8185
-){}
8261
+) {}
81868262
81878263 function directiveLinkingFn(
81888264 /* nodesetLinkingFn */ nodesetLinkingFn,
....@@ -8190,7 +8266,7 @@
81908266 /* Node */ node,
81918267 /* Element */ rootElement,
81928268 /* function(Function) */ boundTranscludeFn
8193
-){}
8269
+) {}
81948270
81958271 function tokenDifference(str1, str2) {
81968272 var values = '',
....@@ -8198,10 +8274,10 @@
81988274 tokens2 = str2.split(/\s+/);
81998275
82008276 outer:
8201
- for(var i = 0; i < tokens1.length; i++) {
8277
+ for (var i = 0; i < tokens1.length; i++) {
82028278 var token = tokens1[i];
8203
- for(var j = 0; j < tokens2.length; j++) {
8204
- if(token == tokens2[j]) continue outer;
8279
+ for (var j = 0; j < tokens2.length; j++) {
8280
+ if (token == tokens2[j]) continue outer;
82058281 }
82068282 values += (values.length > 0 ? ' ' : '') + token;
82078283 }
....@@ -8284,6 +8360,10 @@
82848360 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
82858361 * `window` object (not recommended)
82868362 *
8363
+ * The string can use the `controller as property` syntax, where the controller instance is published
8364
+ * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
8365
+ * to work correctly.
8366
+ *
82878367 * @param {Object} locals Injection locals for Controller.
82888368 * @return {Object} Instance of given controller.
82898369 *
....@@ -8307,7 +8387,7 @@
83078387 identifier = ident;
83088388 }
83098389
8310
- if(isString(expression)) {
8390
+ if (isString(expression)) {
83118391 match = expression.match(CNTRL_REG),
83128392 constructor = match[1],
83138393 identifier = identifier || match[3];
....@@ -8329,10 +8409,10 @@
83298409 //
83308410 // This feature is not intended for use by applications, and is thus not documented
83318411 // publicly.
8332
- var Constructor = function() {};
8333
- Constructor.prototype = (isArray(expression) ?
8412
+ // Object creation: http://jsperf.com/create-constructor/2
8413
+ var controllerPrototype = (isArray(expression) ?
83348414 expression[expression.length - 1] : expression).prototype;
8335
- instance = new Constructor();
8415
+ instance = Object.create(controllerPrototype);
83368416
83378417 if (identifier) {
83388418 addIdentifier(locals, identifier, instance, constructor || expression.name);
....@@ -8393,8 +8473,8 @@
83938473 </file>
83948474 </example>
83958475 */
8396
-function $DocumentProvider(){
8397
- this.$get = ['$window', function(window){
8476
+function $DocumentProvider() {
8477
+ this.$get = ['$window', function(window) {
83988478 return jqLite(window.document);
83998479 }];
84008480 }
....@@ -8415,8 +8495,8 @@
84158495 * ## Example:
84168496 *
84178497 * ```js
8418
- * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () {
8419
- * return function (exception, cause) {
8498
+ * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
8499
+ * return function(exception, cause) {
84208500 * exception.message += ' (caused by "' + cause + '")';
84218501 * throw exception;
84228502 * };
....@@ -8447,6 +8527,25 @@
84478527 }];
84488528 }
84498529
8530
+var APPLICATION_JSON = 'application/json';
8531
+var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
8532
+var JSON_START = /^\s*(\[|\{[^\{])/;
8533
+var JSON_END = /[\}\]]\s*$/;
8534
+var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
8535
+
8536
+function defaultHttpResponseTransform(data, headers) {
8537
+ if (isString(data)) {
8538
+ // strip json vulnerability protection prefix
8539
+ data = data.replace(JSON_PROTECTION_PREFIX, '');
8540
+ var contentType = headers('Content-Type');
8541
+ if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0 && data.trim()) ||
8542
+ (JSON_START.test(data) && JSON_END.test(data))) {
8543
+ data = fromJson(data);
8544
+ }
8545
+ }
8546
+ return data;
8547
+}
8548
+
84508549 /**
84518550 * Parse headers into key value object
84528551 *
....@@ -8454,7 +8553,7 @@
84548553 * @returns {Object} Parsed headers as key value object
84558554 */
84568555 function parseHeaders(headers) {
8457
- var parsed = {}, key, val, i;
8556
+ var parsed = createMap(), key, val, i;
84588557
84598558 if (!headers) return parsed;
84608559
....@@ -8491,7 +8590,11 @@
84918590 if (!headersObj) headersObj = parseHeaders(headers);
84928591
84938592 if (name) {
8494
- return headersObj[lowercase(name)] || null;
8593
+ var value = headersObj[lowercase(name)];
8594
+ if (value === void 0) {
8595
+ value = null;
8596
+ }
8597
+ return value;
84958598 }
84968599
84978600 return headersObj;
....@@ -8533,18 +8636,17 @@
85338636 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
85348637 * */
85358638 function $HttpProvider() {
8536
- var JSON_START = /^\s*(\[|\{[^\{])/,
8537
- JSON_END = /[\}\]]\s*$/,
8538
- PROTECTION_PREFIX = /^\)\]\}',?\n/,
8539
- APPLICATION_JSON = 'application/json',
8540
- CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
8541
-
85428639 /**
85438640 * @ngdoc property
85448641 * @name $httpProvider#defaults
85458642 * @description
85468643 *
85478644 * Object containing default values for all {@link ng.$http $http} requests.
8645
+ *
8646
+ * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
8647
+ * that will provide the cache for all requests who set their `cache` property to `true`.
8648
+ * If you set the `default.cache = false` then only requests that specify their own custom
8649
+ * cache object will be cached. See {@link $http#caching $http Caching} for more information.
85488650 *
85498651 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
85508652 * Defaults value is `'XSRF-TOKEN'`.
....@@ -8559,21 +8661,11 @@
85598661 * - **`defaults.headers.post`**
85608662 * - **`defaults.headers.put`**
85618663 * - **`defaults.headers.patch`**
8664
+ *
85628665 **/
85638666 var defaults = this.defaults = {
85648667 // transform incoming response data
8565
- transformResponse: [function defaultHttpResponseTransform(data, headers) {
8566
- if (isString(data)) {
8567
- // strip json vulnerability protection prefix
8568
- data = data.replace(PROTECTION_PREFIX, '');
8569
- var contentType = headers('Content-Type');
8570
- if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
8571
- (JSON_START.test(data) && JSON_END.test(data))) {
8572
- data = fromJson(data);
8573
- }
8574
- }
8575
- return data;
8576
- }],
8668
+ transformResponse: [defaultHttpResponseTransform],
85778669
85788670 // transform outgoing request data
85798671 transformRequest: [function(d) {
....@@ -8623,9 +8715,18 @@
86238715 };
86248716
86258717 /**
8626
- * Are ordered by request, i.e. they are applied in the same order as the
8718
+ * @ngdoc property
8719
+ * @name $httpProvider#interceptors
8720
+ * @description
8721
+ *
8722
+ * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
8723
+ * pre-processing of request or postprocessing of responses.
8724
+ *
8725
+ * These service factories are ordered by request, i.e. they are applied in the same order as the
86278726 * array, on request, but reverse order, on response.
8628
- */
8727
+ *
8728
+ * {@link ng.$http#interceptors Interceptors detailed info}
8729
+ **/
86298730 var interceptorFactories = this.interceptors = [];
86308731
86318732 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
....@@ -8775,6 +8876,21 @@
87758876 * In addition, you can supply a `headers` property in the config object passed when
87768877 * calling `$http(config)`, which overrides the defaults without changing them globally.
87778878 *
8879
+ * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
8880
+ * Use the `headers` property, setting the desired header to `undefined`. For example:
8881
+ *
8882
+ * ```js
8883
+ * var req = {
8884
+ * method: 'POST',
8885
+ * url: 'http://example.com',
8886
+ * headers: {
8887
+ * 'Content-Type': undefined
8888
+ * },
8889
+ * data: { test: 'test' },
8890
+ * }
8891
+ *
8892
+ * $http(req).success(function(){...}).error(function(){...});
8893
+ * ```
87788894 *
87798895 * ## Transforming Requests and Responses
87808896 *
....@@ -9021,12 +9137,14 @@
90219137 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
90229138 * transform function or an array of such functions. The transform function takes the http
90239139 * request body and headers and returns its transformed (typically serialized) version.
9024
- * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
9140
+ * See {@link ng.$http#overriding-the-default-transformations-per-request
9141
+ * Overriding the Default Transformations}
90259142 * - **transformResponse** –
90269143 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
90279144 * transform function or an array of such functions. The transform function takes the http
90289145 * response body and headers and returns its transformed (typically deserialized) version.
9029
- * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
9146
+ * See {@link ng.$http#overriding-the-default-transformations-per-request
9147
+ * Overriding the Default Transformations}
90309148 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
90319149 * GET request, otherwise if a cache instance built with
90329150 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
....@@ -9154,6 +9272,10 @@
91549272 };
91559273 var headers = mergeHeaders(requestConfig);
91569274
9275
+ if (!angular.isObject(requestConfig)) {
9276
+ throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
9277
+ }
9278
+
91579279 extend(config, requestConfig);
91589280 config.headers = headers;
91599281 config.method = uppercase(config.method);
....@@ -9192,7 +9314,7 @@
91929314 }
91939315 });
91949316
9195
- while(chain.length) {
9317
+ while (chain.length) {
91969318 var thenFn = chain.shift();
91979319 var rejectFn = chain.shift();
91989320
....@@ -9432,8 +9554,7 @@
94329554 if (isDefined(cachedResp)) {
94339555 if (isPromiseLike(cachedResp)) {
94349556 // cached request has already been sent, but there is no response yet
9435
- cachedResp.then(removePendingReq, removePendingReq);
9436
- return cachedResp;
9557
+ cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
94379558 } else {
94389559 // serving from cache
94399560 if (isArray(cachedResp)) {
....@@ -9507,10 +9628,13 @@
95079628 status: status,
95089629 headers: headersGetter(headers),
95099630 config: config,
9510
- statusText : statusText
9631
+ statusText: statusText
95119632 });
95129633 }
95139634
9635
+ function resolvePromiseWithResult(result) {
9636
+ resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
9637
+ }
95149638
95159639 function removePendingReq() {
95169640 var idx = $http.pendingRequests.indexOf(config);
....@@ -9528,7 +9652,7 @@
95289652
95299653 forEach(value, function(v) {
95309654 if (isObject(v)) {
9531
- if (isDate(v)){
9655
+ if (isDate(v)) {
95329656 v = v.toISOString();
95339657 } else {
95349658 v = toJson(v);
....@@ -9538,7 +9662,7 @@
95389662 encodeUriQuery(v));
95399663 });
95409664 });
9541
- if(parts.length > 0) {
9665
+ if (parts.length > 0) {
95429666 url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
95439667 }
95449668 return url;
....@@ -9625,7 +9749,7 @@
96259749 statusText);
96269750 };
96279751
9628
- var requestError = function () {
9752
+ var requestError = function() {
96299753 // The response is always empty
96309754 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
96319755 completeRequest(callback, -1, null, null, '');
....@@ -9672,7 +9796,9 @@
96729796
96739797 function completeRequest(callback, status, response, headersString, statusText) {
96749798 // cancel timeout and subsequent timeout promise resolution
9675
- timeoutId && $browserDefer.cancel(timeoutId);
9799
+ if (timeoutId !== undefined) {
9800
+ $browserDefer.cancel(timeoutId);
9801
+ }
96769802 jsonpDone = xhr = null;
96779803
96789804 callback(status, response, headersString, statusText);
....@@ -9767,7 +9893,7 @@
97679893 * @param {string=} value new value to set the starting symbol to.
97689894 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
97699895 */
9770
- this.startSymbol = function(value){
9896
+ this.startSymbol = function(value) {
97719897 if (value) {
97729898 startSymbol = value;
97739899 return this;
....@@ -9785,7 +9911,7 @@
97859911 * @param {string=} value new value to set the ending symbol to.
97869912 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
97879913 */
9788
- this.endSymbol = function(value){
9914
+ this.endSymbol = function(value) {
97899915 if (value) {
97909916 endSymbol = value;
97919917 return this;
....@@ -9911,9 +10037,9 @@
991110037 concat = [],
991210038 expressionPositions = [];
991310039
9914
- while(index < textLength) {
9915
- if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
9916
- ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
10040
+ while (index < textLength) {
10041
+ if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
10042
+ ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
991710043 if (index !== startIndex) {
991810044 concat.push(unescapeText(text.substring(index, startIndex)));
991910045 }
....@@ -9947,20 +10073,20 @@
994710073
994810074 if (!mustHaveExpression || expressions.length) {
994910075 var compute = function(values) {
9950
- for(var i = 0, ii = expressions.length; i < ii; i++) {
10076
+ for (var i = 0, ii = expressions.length; i < ii; i++) {
995110077 if (allOrNothing && isUndefined(values[i])) return;
995210078 concat[expressionPositions[i]] = values[i];
995310079 }
995410080 return concat.join('');
995510081 };
995610082
9957
- var getValue = function (value) {
10083
+ var getValue = function(value) {
995810084 return trustedContext ?
995910085 $sce.getTrusted(trustedContext, value) :
996010086 $sce.valueOf(value);
996110087 };
996210088
9963
- var stringify = function (value) {
10089
+ var stringify = function(value) {
996410090 if (value == null) { // null || undefined
996510091 return '';
996610092 }
....@@ -9988,7 +10114,7 @@
998810114 }
998910115
999010116 return compute(values);
9991
- } catch(err) {
10117
+ } catch (err) {
999210118 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
999310119 err.toString());
999410120 $exceptionHandler(newErr);
....@@ -9998,7 +10124,7 @@
999810124 // all of these properties are undocumented for now
999910125 exp: text, //just for compatibility with regular watchers created via $watch
1000010126 expressions: expressions,
10001
- $$watchDelegate: function (scope, listener, objectEquality) {
10127
+ $$watchDelegate: function(scope, listener, objectEquality) {
1000210128 var lastValue;
1000310129 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
1000410130 var currValue = compute(values);
....@@ -10018,8 +10144,9 @@
1001810144
1001910145 function parseStringifyInterceptor(value) {
1002010146 try {
10021
- return stringify(getValue(value));
10022
- } catch(err) {
10147
+ value = getValue(value);
10148
+ return allOrNothing && !isDefined(value) ? value : stringify(value);
10149
+ } catch (err) {
1002310150 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
1002410151 err.toString());
1002510152 $exceptionHandler(newErr);
....@@ -10119,33 +10246,33 @@
1011910246 * // Don't start a new fight if we are already fighting
1012010247 * if ( angular.isDefined(stop) ) return;
1012110248 *
10122
- * stop = $interval(function() {
10123
- * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
10124
- * $scope.blood_1 = $scope.blood_1 - 3;
10125
- * $scope.blood_2 = $scope.blood_2 - 4;
10126
- * } else {
10127
- * $scope.stopFight();
10249
+ * stop = $interval(function() {
10250
+ * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
10251
+ * $scope.blood_1 = $scope.blood_1 - 3;
10252
+ * $scope.blood_2 = $scope.blood_2 - 4;
10253
+ * } else {
10254
+ * $scope.stopFight();
10255
+ * }
10256
+ * }, 100);
10257
+ * };
10258
+ *
10259
+ * $scope.stopFight = function() {
10260
+ * if (angular.isDefined(stop)) {
10261
+ * $interval.cancel(stop);
10262
+ * stop = undefined;
1012810263 * }
10129
- * }, 100);
10130
- * };
10264
+ * };
1013110265 *
10132
- * $scope.stopFight = function() {
10133
- * if (angular.isDefined(stop)) {
10134
- * $interval.cancel(stop);
10135
- * stop = undefined;
10136
- * }
10137
- * };
10266
+ * $scope.resetFight = function() {
10267
+ * $scope.blood_1 = 100;
10268
+ * $scope.blood_2 = 120;
10269
+ * };
1013810270 *
10139
- * $scope.resetFight = function() {
10140
- * $scope.blood_1 = 100;
10141
- * $scope.blood_2 = 120;
10142
- * };
10143
- *
10144
- * $scope.$on('$destroy', function() {
10145
- * // Make sure that the interval is destroyed too
10146
- * $scope.stopFight();
10147
- * });
10148
- * }])
10271
+ * $scope.$on('$destroy', function() {
10272
+ * // Make sure that the interval is destroyed too
10273
+ * $scope.stopFight();
10274
+ * });
10275
+ * }])
1014910276 * // Register the 'myCurrentTime' directive factory method.
1015010277 * // We inject $interval and dateFilter service since the factory method is DI.
1015110278 * .directive('myCurrentTime', ['$interval', 'dateFilter',
....@@ -10258,7 +10385,7 @@
1025810385 *
1025910386 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
1026010387 */
10261
-function $LocaleProvider(){
10388
+function $LocaleProvider() {
1026210389 this.$get = function() {
1026310390 return {
1026410391 id: 'en-us',
....@@ -10301,7 +10428,7 @@
1030110428 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
1030210429 AMPMS: ['AM','PM'],
1030310430 medium: 'MMM d, y h:mm:ss a',
10304
- short: 'M/d/yy h:mm a',
10431
+ 'short': 'M/d/yy h:mm a',
1030510432 fullDate: 'EEEE, MMMM d, y',
1030610433 longDate: 'MMMM d, y',
1030710434 mediumDate: 'MMM d, y',
....@@ -10342,8 +10469,8 @@
1034210469 return segments.join('/');
1034310470 }
1034410471
10345
-function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
10346
- var parsedUrl = urlResolve(absoluteUrl, appBase);
10472
+function parseAbsoluteUrl(absoluteUrl, locationObj) {
10473
+ var parsedUrl = urlResolve(absoluteUrl);
1034710474
1034810475 locationObj.$$protocol = parsedUrl.protocol;
1034910476 locationObj.$$host = parsedUrl.hostname;
....@@ -10351,12 +10478,12 @@
1035110478 }
1035210479
1035310480
10354
-function parseAppUrl(relativeUrl, locationObj, appBase) {
10481
+function parseAppUrl(relativeUrl, locationObj) {
1035510482 var prefixed = (relativeUrl.charAt(0) !== '/');
1035610483 if (prefixed) {
1035710484 relativeUrl = '/' + relativeUrl;
1035810485 }
10359
- var match = urlResolve(relativeUrl, appBase);
10486
+ var match = urlResolve(relativeUrl);
1036010487 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
1036110488 match.pathname.substring(1) : match.pathname);
1036210489 locationObj.$$search = parseKeyValue(match.search);
....@@ -10388,6 +10515,10 @@
1038810515 return index == -1 ? url : url.substr(0, index);
1038910516 }
1039010517
10518
+function trimEmptyHash(url) {
10519
+ return url.replace(/(#.+)|#$/, '$1');
10520
+}
10521
+
1039110522
1039210523 function stripFile(url) {
1039310524 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
....@@ -10411,12 +10542,12 @@
1041110542 this.$$html5 = true;
1041210543 basePrefix = basePrefix || '';
1041310544 var appBaseNoFile = stripFile(appBase);
10414
- parseAbsoluteUrl(appBase, this, appBase);
10545
+ parseAbsoluteUrl(appBase, this);
1041510546
1041610547
1041710548 /**
1041810549 * Parse given html5 (regular) url string into properties
10419
- * @param {string} newAbsoluteUrl HTML5 url
10550
+ * @param {string} url HTML5 url
1042010551 * @private
1042110552 */
1042210553 this.$$parse = function(url) {
....@@ -10426,7 +10557,7 @@
1042610557 appBaseNoFile);
1042710558 }
1042810559
10429
- parseAppUrl(pathUrl, this, appBase);
10560
+ parseAppUrl(pathUrl, this);
1043010561
1043110562 if (!this.$$path) {
1043210563 this.$$path = '/';
....@@ -10457,14 +10588,14 @@
1045710588 var appUrl, prevAppUrl;
1045810589 var rewrittenUrl;
1045910590
10460
- if ( (appUrl = beginsWith(appBase, url)) !== undefined ) {
10591
+ if ((appUrl = beginsWith(appBase, url)) !== undefined) {
1046110592 prevAppUrl = appUrl;
10462
- if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) {
10593
+ if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) {
1046310594 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
1046410595 } else {
1046510596 rewrittenUrl = appBase + prevAppUrl;
1046610597 }
10467
- } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) {
10598
+ } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) {
1046810599 rewrittenUrl = appBaseNoFile + appUrl;
1046910600 } else if (appBaseNoFile == url + '/') {
1047010601 rewrittenUrl = appBaseNoFile;
....@@ -10489,7 +10620,7 @@
1048910620 function LocationHashbangUrl(appBase, hashPrefix) {
1049010621 var appBaseNoFile = stripFile(appBase);
1049110622
10492
- parseAbsoluteUrl(appBase, this, appBase);
10623
+ parseAbsoluteUrl(appBase, this);
1049310624
1049410625
1049510626 /**
....@@ -10499,17 +10630,26 @@
1049910630 */
1050010631 this.$$parse = function(url) {
1050110632 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
10502
- var withoutHashUrl = withoutBaseUrl.charAt(0) == '#'
10503
- ? beginsWith(hashPrefix, withoutBaseUrl)
10504
- : (this.$$html5)
10505
- ? withoutBaseUrl
10506
- : '';
10633
+ var withoutHashUrl;
1050710634
10508
- if (!isString(withoutHashUrl)) {
10509
- throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
10510
- hashPrefix);
10635
+ if (withoutBaseUrl.charAt(0) === '#') {
10636
+
10637
+ // The rest of the url starts with a hash so we have
10638
+ // got either a hashbang path or a plain hash fragment
10639
+ withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
10640
+ if (isUndefined(withoutHashUrl)) {
10641
+ // There was no hashbang prefix so we just have a hash fragment
10642
+ withoutHashUrl = withoutBaseUrl;
10643
+ }
10644
+
10645
+ } else {
10646
+ // There was no hashbang path nor hash fragment:
10647
+ // If we are in HTML5 mode we use what is left as the path;
10648
+ // Otherwise we ignore what is left
10649
+ withoutHashUrl = this.$$html5 ? withoutBaseUrl : '';
1051110650 }
10512
- parseAppUrl(withoutHashUrl, this, appBase);
10651
+
10652
+ parseAppUrl(withoutHashUrl, this);
1051310653
1051410654 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
1051510655
....@@ -10526,7 +10666,7 @@
1052610666 * Inside of Angular, we're always using pathnames that
1052710667 * do not include drive names for routing.
1052810668 */
10529
- function removeWindowsDriveName (path, url, base) {
10669
+ function removeWindowsDriveName(path, url, base) {
1053010670 /*
1053110671 Matches paths for file protocol on windows,
1053210672 such as /C:/foo/bar, and captures only /foo/bar.
....@@ -10563,7 +10703,7 @@
1056310703 };
1056410704
1056510705 this.$$parseLinkUrl = function(url, relHref) {
10566
- if(stripHash(appBase) == stripHash(url)) {
10706
+ if (stripHash(appBase) == stripHash(url)) {
1056710707 this.$$parse(url);
1056810708 return true;
1056910709 }
....@@ -10598,11 +10738,11 @@
1059810738 var rewrittenUrl;
1059910739 var appUrl;
1060010740
10601
- if ( appBase == stripHash(url) ) {
10741
+ if (appBase == stripHash(url)) {
1060210742 rewrittenUrl = url;
10603
- } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
10743
+ } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
1060410744 rewrittenUrl = appBase + hashPrefix + appUrl;
10605
- } else if ( appBaseNoFile === url + '/') {
10745
+ } else if (appBaseNoFile === url + '/') {
1060610746 rewrittenUrl = appBaseNoFile;
1060710747 }
1060810748 if (rewrittenUrl) {
....@@ -10647,6 +10787,13 @@
1064710787 * Return full url representation with all segments encoded according to rules specified in
1064810788 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
1064910789 *
10790
+ *
10791
+ * ```js
10792
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
10793
+ * var absUrl = $location.absUrl();
10794
+ * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
10795
+ * ```
10796
+ *
1065010797 * @return {string} full url
1065110798 */
1065210799 absUrl: locationGetter('$$absUrl'),
....@@ -10662,6 +10809,13 @@
1066210809 *
1066310810 * Change path, search and hash, when called with parameter and return `$location`.
1066410811 *
10812
+ *
10813
+ * ```js
10814
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
10815
+ * var url = $location.url();
10816
+ * // => "/some/path?foo=bar&baz=xoxo"
10817
+ * ```
10818
+ *
1066510819 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
1066610820 * @return {string} url
1066710821 */
....@@ -10670,8 +10824,8 @@
1067010824 return this.$$url;
1067110825
1067210826 var match = PATH_MATCH.exec(url);
10673
- if (match[1]) this.path(decodeURIComponent(match[1]));
10674
- if (match[2] || match[1]) this.search(match[3] || '');
10827
+ if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
10828
+ if (match[2] || match[1] || url === '') this.search(match[3] || '');
1067510829 this.hash(match[5] || '');
1067610830
1067710831 return this;
....@@ -10686,6 +10840,13 @@
1068610840 *
1068710841 * Return protocol of current url.
1068810842 *
10843
+ *
10844
+ * ```js
10845
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
10846
+ * var protocol = $location.protocol();
10847
+ * // => "http"
10848
+ * ```
10849
+ *
1068910850 * @return {string} protocol of current url
1069010851 */
1069110852 protocol: locationGetter('$$protocol'),
....@@ -10699,6 +10860,13 @@
1069910860 *
1070010861 * Return host of current url.
1070110862 *
10863
+ *
10864
+ * ```js
10865
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
10866
+ * var host = $location.host();
10867
+ * // => "example.com"
10868
+ * ```
10869
+ *
1070210870 * @return {string} host of current url.
1070310871 */
1070410872 host: locationGetter('$$host'),
....@@ -10711,6 +10879,13 @@
1071110879 * This method is getter only.
1071210880 *
1071310881 * Return port of current url.
10882
+ *
10883
+ *
10884
+ * ```js
10885
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
10886
+ * var port = $location.port();
10887
+ * // => 80
10888
+ * ```
1071410889 *
1071510890 * @return {Number} port
1071610891 */
....@@ -10729,6 +10904,13 @@
1072910904 *
1073010905 * Note: Path should always begin with forward slash (/), this method will add the forward slash
1073110906 * if it is missing.
10907
+ *
10908
+ *
10909
+ * ```js
10910
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
10911
+ * var path = $location.path();
10912
+ * // => "/some/path"
10913
+ * ```
1073210914 *
1073310915 * @param {(string|number)=} path New path
1073410916 * @return {string} path
....@@ -10755,10 +10937,9 @@
1075510937 * var searchObject = $location.search();
1075610938 * // => {foo: 'bar', baz: 'xoxo'}
1075710939 *
10758
- *
1075910940 * // set foo to 'yipee'
1076010941 * $location.search('foo', 'yipee');
10761
- * // => $location
10942
+ * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
1076210943 * ```
1076310944 *
1076410945 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
....@@ -10828,6 +11009,13 @@
1082811009 *
1082911010 * Change hash fragment when called with parameter and return `$location`.
1083011011 *
11012
+ *
11013
+ * ```js
11014
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
11015
+ * var hash = $location.hash();
11016
+ * // => "hashValue"
11017
+ * ```
11018
+ *
1083111019 * @param {(string|number)=} hash New hash fragment
1083211020 * @return {string} hash
1083311021 */
....@@ -10849,7 +11037,7 @@
1084911037 }
1085011038 };
1085111039
10852
-forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) {
11040
+forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
1085311041 Location.prototype = Object.create(locationPrototype);
1085411042
1085511043 /**
....@@ -10941,7 +11129,7 @@
1094111129 * @description
1094211130 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
1094311131 */
10944
-function $LocationProvider(){
11132
+function $LocationProvider() {
1094511133 var hashPrefix = '',
1094611134 html5Mode = {
1094711135 enabled: false,
....@@ -11048,7 +11236,7 @@
1104811236 */
1104911237
1105011238 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
11051
- function( $rootScope, $browser, $sniffer, $rootElement) {
11239
+ function($rootScope, $browser, $sniffer, $rootElement) {
1105211240 var $location,
1105311241 LocationMode,
1105411242 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
....@@ -11149,11 +11337,19 @@
1114911337 $rootScope.$evalAsync(function() {
1115011338 var oldUrl = $location.absUrl();
1115111339 var oldState = $location.$$state;
11340
+ var defaultPrevented;
1115211341
1115311342 $location.$$parse(newUrl);
1115411343 $location.$$state = newState;
11155
- if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
11156
- newState, oldState).defaultPrevented) {
11344
+
11345
+ defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
11346
+ newState, oldState).defaultPrevented;
11347
+
11348
+ // if the location was changed by a `$locationChangeStart` handler then stop
11349
+ // processing this location change
11350
+ if ($location.absUrl() !== newUrl) return;
11351
+
11352
+ if (defaultPrevented) {
1115711353 $location.$$parse(oldUrl);
1115811354 $location.$$state = oldState;
1115911355 setBrowserUrlWithFallback(oldUrl, false, oldState);
....@@ -11167,23 +11363,31 @@
1116711363
1116811364 // update browser
1116911365 $rootScope.$watch(function $locationWatch() {
11170
- var oldUrl = $browser.url();
11366
+ var oldUrl = trimEmptyHash($browser.url());
11367
+ var newUrl = trimEmptyHash($location.absUrl());
1117111368 var oldState = $browser.state();
1117211369 var currentReplace = $location.$$replace;
11173
- var urlOrStateChanged = oldUrl !== $location.absUrl() ||
11370
+ var urlOrStateChanged = oldUrl !== newUrl ||
1117411371 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
1117511372
1117611373 if (initializing || urlOrStateChanged) {
1117711374 initializing = false;
1117811375
1117911376 $rootScope.$evalAsync(function() {
11180
- if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
11181
- $location.$$state, oldState).defaultPrevented) {
11377
+ var newUrl = $location.absUrl();
11378
+ var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
11379
+ $location.$$state, oldState).defaultPrevented;
11380
+
11381
+ // if the location was changed by a `$locationChangeStart` handler then stop
11382
+ // processing this location change
11383
+ if ($location.absUrl() !== newUrl) return;
11384
+
11385
+ if (defaultPrevented) {
1118211386 $location.$$parse(oldUrl);
1118311387 $location.$$state = oldState;
1118411388 } else {
1118511389 if (urlOrStateChanged) {
11186
- setBrowserUrlWithFallback($location.absUrl(), currentReplace,
11390
+ setBrowserUrlWithFallback(newUrl, currentReplace,
1118711391 oldState === $location.$$state ? null : $location.$$state);
1118811392 }
1118911393 afterLocationChange(oldUrl, oldState);
....@@ -11249,7 +11453,7 @@
1124911453 * @description
1125011454 * Use the `$logProvider` to configure how the application logs messages
1125111455 */
11252
-function $LogProvider(){
11456
+function $LogProvider() {
1125311457 var debug = true,
1125411458 self = this;
1125511459
....@@ -11269,7 +11473,7 @@
1126911473 }
1127011474 };
1127111475
11272
- this.$get = ['$window', function($window){
11476
+ this.$get = ['$window', function($window) {
1127311477 return {
1127411478 /**
1127511479 * @ngdoc method
....@@ -11314,7 +11518,7 @@
1131411518 * @description
1131511519 * Write a debug message
1131611520 */
11317
- debug: (function () {
11521
+ debug: (function() {
1131811522 var fn = consoleLog('debug');
1131911523
1132011524 return function() {
....@@ -11373,7 +11577,7 @@
1137311577 // Sandboxing Angular Expressions
1137411578 // ------------------------------
1137511579 // Angular expressions are generally considered safe because these expressions only have direct
11376
-// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by
11580
+// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
1137711581 // obtaining a reference to native JS functions such as the Function constructor.
1137811582 //
1137911583 // As an example, consider the following Angular expression:
....@@ -11382,7 +11586,7 @@
1138211586 //
1138311587 // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
1138411588 // against the expression language, but not to prevent exploits that were enabled by exposing
11385
-// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good
11589
+// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
1138611590 // practice and therefore we are not even trying to protect against interaction with an object
1138711591 // explicitly exposed in this way.
1138811592 //
....@@ -11390,6 +11594,8 @@
1139011594 // window or some DOM object that has a reference to window is published onto a Scope.
1139111595 // Similarly we prevent invocations of function known to be dangerous, as well as assignments to
1139211596 // native objects.
11597
+//
11598
+// See https://docs.angularjs.org/guide/security
1139311599
1139411600
1139511601 function ensureSafeMemberName(name, fullExpression) {
....@@ -11398,7 +11604,7 @@
1139811604 || name === "__proto__") {
1139911605 throw $parseMinErr('isecfld',
1140011606 'Attempting to access a disallowed field in Angular expressions! '
11401
- +'Expression: {0}', fullExpression);
11607
+ + 'Expression: {0}', fullExpression);
1140211608 }
1140311609 return name;
1140411610 }
....@@ -11467,7 +11673,7 @@
1146711673
1146811674 //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
1146911675 var OPERATORS = extend(createMap(), {
11470
- '+':function(self, locals, a,b){
11676
+ '+':function(self, locals, a, b) {
1147111677 a=a(self, locals); b=b(self, locals);
1147211678 if (isDefined(a)) {
1147311679 if (isDefined(b)) {
....@@ -11475,25 +11681,25 @@
1147511681 }
1147611682 return a;
1147711683 }
11478
- return isDefined(b)?b:undefined;},
11479
- '-':function(self, locals, a,b){
11684
+ return isDefined(b) ? b : undefined;},
11685
+ '-':function(self, locals, a, b) {
1148011686 a=a(self, locals); b=b(self, locals);
11481
- return (isDefined(a)?a:0)-(isDefined(b)?b:0);
11687
+ return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);
1148211688 },
11483
- '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
11484
- '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
11485
- '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
11486
- '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
11487
- '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
11488
- '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
11489
- '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
11490
- '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
11491
- '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
11492
- '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
11493
- '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
11494
- '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
11495
- '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
11496
- '!':function(self, locals, a){return !a(self, locals);},
11689
+ '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);},
11690
+ '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);},
11691
+ '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);},
11692
+ '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);},
11693
+ '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);},
11694
+ '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);},
11695
+ '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);},
11696
+ '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);},
11697
+ '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);},
11698
+ '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);},
11699
+ '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);},
11700
+ '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);},
11701
+ '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);},
11702
+ '!':function(self, locals, a) {return !a(self, locals);},
1149711703
1149811704 //Tokenized as operators but parsed as assignment/filters
1149911705 '=':true,
....@@ -11508,54 +11714,41 @@
1150811714 /**
1150911715 * @constructor
1151011716 */
11511
-var Lexer = function (options) {
11717
+var Lexer = function(options) {
1151211718 this.options = options;
1151311719 };
1151411720
1151511721 Lexer.prototype = {
1151611722 constructor: Lexer,
1151711723
11518
- lex: function (text) {
11724
+ lex: function(text) {
1151911725 this.text = text;
1152011726 this.index = 0;
11521
- this.ch = undefined;
1152211727 this.tokens = [];
1152311728
1152411729 while (this.index < this.text.length) {
11525
- this.ch = this.text.charAt(this.index);
11526
- if (this.is('"\'')) {
11527
- this.readString(this.ch);
11528
- } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) {
11730
+ var ch = this.text.charAt(this.index);
11731
+ if (ch === '"' || ch === "'") {
11732
+ this.readString(ch);
11733
+ } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
1152911734 this.readNumber();
11530
- } else if (this.isIdent(this.ch)) {
11735
+ } else if (this.isIdent(ch)) {
1153111736 this.readIdent();
11532
- } else if (this.is('(){}[].,;:?')) {
11533
- this.tokens.push({
11534
- index: this.index,
11535
- text: this.ch
11536
- });
11737
+ } else if (this.is(ch, '(){}[].,;:?')) {
11738
+ this.tokens.push({index: this.index, text: ch});
1153711739 this.index++;
11538
- } else if (this.isWhitespace(this.ch)) {
11740
+ } else if (this.isWhitespace(ch)) {
1153911741 this.index++;
1154011742 } else {
11541
- var ch2 = this.ch + this.peek();
11743
+ var ch2 = ch + this.peek();
1154211744 var ch3 = ch2 + this.peek(2);
11543
- var fn = OPERATORS[this.ch];
11544
- var fn2 = OPERATORS[ch2];
11545
- var fn3 = OPERATORS[ch3];
11546
- if (fn3) {
11547
- this.tokens.push({index: this.index, text: ch3, fn: fn3});
11548
- this.index += 3;
11549
- } else if (fn2) {
11550
- this.tokens.push({index: this.index, text: ch2, fn: fn2});
11551
- this.index += 2;
11552
- } else if (fn) {
11553
- this.tokens.push({
11554
- index: this.index,
11555
- text: this.ch,
11556
- fn: fn
11557
- });
11558
- this.index += 1;
11745
+ var op1 = OPERATORS[ch];
11746
+ var op2 = OPERATORS[ch2];
11747
+ var op3 = OPERATORS[ch3];
11748
+ if (op1 || op2 || op3) {
11749
+ var token = op3 ? ch3 : (op2 ? ch2 : ch);
11750
+ this.tokens.push({index: this.index, text: token, operator: true});
11751
+ this.index += token.length;
1155911752 } else {
1156011753 this.throwError('Unexpected next character ', this.index, this.index + 1);
1156111754 }
....@@ -11564,8 +11757,8 @@
1156411757 return this.tokens;
1156511758 },
1156611759
11567
- is: function(chars) {
11568
- return chars.indexOf(this.ch) !== -1;
11760
+ is: function(ch, chars) {
11761
+ return chars.indexOf(ch) !== -1;
1156911762 },
1157011763
1157111764 peek: function(i) {
....@@ -11574,7 +11767,7 @@
1157411767 },
1157511768
1157611769 isNumber: function(ch) {
11577
- return ('0' <= ch && ch <= '9');
11770
+ return ('0' <= ch && ch <= '9') && typeof ch === "string";
1157811771 },
1157911772
1158011773 isWhitespace: function(ch) {
....@@ -11627,79 +11820,28 @@
1162711820 }
1162811821 this.index++;
1162911822 }
11630
- number = 1 * number;
1163111823 this.tokens.push({
1163211824 index: start,
1163311825 text: number,
1163411826 constant: true,
11635
- fn: function() { return number; }
11827
+ value: Number(number)
1163611828 });
1163711829 },
1163811830
1163911831 readIdent: function() {
11640
- var expression = this.text;
11641
-
11642
- var ident = '';
1164311832 var start = this.index;
11644
-
11645
- var lastDot, peekIndex, methodName, ch;
11646
-
1164711833 while (this.index < this.text.length) {
11648
- ch = this.text.charAt(this.index);
11649
- if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) {
11650
- if (ch === '.') lastDot = this.index;
11651
- ident += ch;
11652
- } else {
11834
+ var ch = this.text.charAt(this.index);
11835
+ if (!(this.isIdent(ch) || this.isNumber(ch))) {
1165311836 break;
1165411837 }
1165511838 this.index++;
1165611839 }
11657
-
11658
- //check if the identifier ends with . and if so move back one char
11659
- if (lastDot && ident[ident.length - 1] === '.') {
11660
- this.index--;
11661
- ident = ident.slice(0, -1);
11662
- lastDot = ident.lastIndexOf('.');
11663
- if (lastDot === -1) {
11664
- lastDot = undefined;
11665
- }
11666
- }
11667
-
11668
- //check if this is not a method invocation and if it is back out to last dot
11669
- if (lastDot) {
11670
- peekIndex = this.index;
11671
- while (peekIndex < this.text.length) {
11672
- ch = this.text.charAt(peekIndex);
11673
- if (ch === '(') {
11674
- methodName = ident.substr(lastDot - start + 1);
11675
- ident = ident.substr(0, lastDot - start);
11676
- this.index = peekIndex;
11677
- break;
11678
- }
11679
- if (this.isWhitespace(ch)) {
11680
- peekIndex++;
11681
- } else {
11682
- break;
11683
- }
11684
- }
11685
- }
11686
-
1168711840 this.tokens.push({
1168811841 index: start,
11689
- text: ident,
11690
- fn: CONSTANTS[ident] || getterFn(ident, this.options, expression)
11842
+ text: this.text.slice(start, this.index),
11843
+ identifier: true
1169111844 });
11692
-
11693
- if (methodName) {
11694
- this.tokens.push({
11695
- index: lastDot,
11696
- text: '.'
11697
- });
11698
- this.tokens.push({
11699
- index: lastDot + 1,
11700
- text: methodName
11701
- });
11702
- }
1170311845 },
1170411846
1170511847 readString: function(quote) {
....@@ -11730,9 +11872,8 @@
1173011872 this.tokens.push({
1173111873 index: start,
1173211874 text: rawString,
11733
- string: string,
1173411875 constant: true,
11735
- fn: function() { return string; }
11876
+ value: string
1173611877 });
1173711878 return;
1173811879 } else {
....@@ -11752,13 +11893,13 @@
1175211893 /**
1175311894 * @constructor
1175411895 */
11755
-var Parser = function (lexer, $filter, options) {
11896
+var Parser = function(lexer, $filter, options) {
1175611897 this.lexer = lexer;
1175711898 this.$filter = $filter;
1175811899 this.options = options;
1175911900 };
1176011901
11761
-Parser.ZERO = extend(function () {
11902
+Parser.ZERO = extend(function() {
1176211903 return 0;
1176311904 }, {
1176411905 sharedGetter: true,
....@@ -11768,7 +11909,7 @@
1176811909 Parser.prototype = {
1176911910 constructor: Parser,
1177011911
11771
- parse: function (text) {
11912
+ parse: function(text) {
1177211913 this.text = text;
1177311914 this.tokens = this.lexer.lex(text);
1177411915
....@@ -11784,7 +11925,7 @@
1178411925 return value;
1178511926 },
1178611927
11787
- primary: function () {
11928
+ primary: function() {
1178811929 var primary;
1178911930 if (this.expect('(')) {
1179011931 primary = this.filterChain();
....@@ -11793,16 +11934,12 @@
1179311934 primary = this.arrayDeclaration();
1179411935 } else if (this.expect('{')) {
1179511936 primary = this.object();
11937
+ } else if (this.peek().identifier) {
11938
+ primary = this.identifier();
11939
+ } else if (this.peek().constant) {
11940
+ primary = this.constant();
1179611941 } else {
11797
- var token = this.expect();
11798
- primary = token.fn;
11799
- if (!primary) {
11800
- this.throwError('not a primary expression', token);
11801
- }
11802
- if (token.constant) {
11803
- primary.constant = true;
11804
- primary.literal = true;
11805
- }
11942
+ this.throwError('not a primary expression', this.peek());
1180611943 }
1180711944
1180811945 var next, context;
....@@ -11836,8 +11973,11 @@
1183611973 },
1183711974
1183811975 peek: function(e1, e2, e3, e4) {
11839
- if (this.tokens.length > 0) {
11840
- var token = this.tokens[0];
11976
+ return this.peekAhead(0, e1, e2, e3, e4);
11977
+ },
11978
+ peekAhead: function(i, e1, e2, e3, e4) {
11979
+ if (this.tokens.length > i) {
11980
+ var token = this.tokens[i];
1184111981 var t = token.text;
1184211982 if (t === e1 || t === e2 || t === e3 || t === e4 ||
1184311983 (!e1 && !e2 && !e3 && !e4)) {
....@@ -11847,7 +11987,7 @@
1184711987 return false;
1184811988 },
1184911989
11850
- expect: function(e1, e2, e3, e4){
11990
+ expect: function(e1, e2, e3, e4) {
1185111991 var token = this.peek(e1, e2, e3, e4);
1185211992 if (token) {
1185311993 this.tokens.shift();
....@@ -11856,13 +11996,20 @@
1185611996 return false;
1185711997 },
1185811998
11859
- consume: function(e1){
11860
- if (!this.expect(e1)) {
11999
+ consume: function(e1) {
12000
+ if (this.tokens.length === 0) {
12001
+ throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
12002
+ }
12003
+
12004
+ var token = this.expect(e1);
12005
+ if (!token) {
1186112006 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
1186212007 }
12008
+ return token;
1186312009 },
1186412010
11865
- unaryFn: function(fn, right) {
12011
+ unaryFn: function(op, right) {
12012
+ var fn = OPERATORS[op];
1186612013 return extend(function $parseUnaryFn(self, locals) {
1186712014 return fn(self, locals, right);
1186812015 }, {
....@@ -11871,12 +12018,35 @@
1187112018 });
1187212019 },
1187312020
11874
- binaryFn: function(left, fn, right, isBranching) {
12021
+ binaryFn: function(left, op, right, isBranching) {
12022
+ var fn = OPERATORS[op];
1187512023 return extend(function $parseBinaryFn(self, locals) {
1187612024 return fn(self, locals, left, right);
1187712025 }, {
1187812026 constant: left.constant && right.constant,
1187912027 inputs: !isBranching && [left, right]
12028
+ });
12029
+ },
12030
+
12031
+ identifier: function() {
12032
+ var id = this.consume().text;
12033
+
12034
+ //Continue reading each `.identifier` unless it is a method invocation
12035
+ while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) {
12036
+ id += this.consume().text + this.consume().text;
12037
+ }
12038
+
12039
+ return CONSTANTS[id] || getterFn(id, this.options, this.text);
12040
+ },
12041
+
12042
+ constant: function() {
12043
+ var value = this.consume().value;
12044
+
12045
+ return extend(function $parseConstant() {
12046
+ return value;
12047
+ }, {
12048
+ constant: true,
12049
+ literal: true
1188012050 });
1188112051 },
1188212052
....@@ -11911,8 +12081,7 @@
1191112081 },
1191212082
1191312083 filter: function(inputFn) {
11914
- var token = this.expect();
11915
- var fn = this.$filter(token.text);
12084
+ var fn = this.$filter(this.consume().text);
1191612085 var argsFn;
1191712086 var args;
1191812087
....@@ -11975,17 +12144,14 @@
1197512144 var token;
1197612145 if ((token = this.expect('?'))) {
1197712146 middle = this.assignment();
11978
- if ((token = this.expect(':'))) {
12147
+ if (this.consume(':')) {
1197912148 var right = this.assignment();
1198012149
11981
- return extend(function $parseTernary(self, locals){
12150
+ return extend(function $parseTernary(self, locals) {
1198212151 return left(self, locals) ? middle(self, locals) : right(self, locals);
1198312152 }, {
1198412153 constant: left.constant && middle.constant && right.constant
1198512154 });
11986
-
11987
- } else {
11988
- this.throwError('expected :', token);
1198912155 }
1199012156 }
1199112157
....@@ -11996,7 +12162,7 @@
1199612162 var left = this.logicalAND();
1199712163 var token;
1199812164 while ((token = this.expect('||'))) {
11999
- left = this.binaryFn(left, token.fn, this.logicalAND(), true);
12165
+ left = this.binaryFn(left, token.text, this.logicalAND(), true);
1200012166 }
1200112167 return left;
1200212168 },
....@@ -12004,8 +12170,8 @@
1200412170 logicalAND: function() {
1200512171 var left = this.equality();
1200612172 var token;
12007
- if ((token = this.expect('&&'))) {
12008
- left = this.binaryFn(left, token.fn, this.logicalAND(), true);
12173
+ while ((token = this.expect('&&'))) {
12174
+ left = this.binaryFn(left, token.text, this.equality(), true);
1200912175 }
1201012176 return left;
1201112177 },
....@@ -12013,8 +12179,8 @@
1201312179 equality: function() {
1201412180 var left = this.relational();
1201512181 var token;
12016
- if ((token = this.expect('==','!=','===','!=='))) {
12017
- left = this.binaryFn(left, token.fn, this.equality());
12182
+ while ((token = this.expect('==','!=','===','!=='))) {
12183
+ left = this.binaryFn(left, token.text, this.relational());
1201812184 }
1201912185 return left;
1202012186 },
....@@ -12022,8 +12188,8 @@
1202212188 relational: function() {
1202312189 var left = this.additive();
1202412190 var token;
12025
- if ((token = this.expect('<', '>', '<=', '>='))) {
12026
- left = this.binaryFn(left, token.fn, this.relational());
12191
+ while ((token = this.expect('<', '>', '<=', '>='))) {
12192
+ left = this.binaryFn(left, token.text, this.additive());
1202712193 }
1202812194 return left;
1202912195 },
....@@ -12032,7 +12198,7 @@
1203212198 var left = this.multiplicative();
1203312199 var token;
1203412200 while ((token = this.expect('+','-'))) {
12035
- left = this.binaryFn(left, token.fn, this.multiplicative());
12201
+ left = this.binaryFn(left, token.text, this.multiplicative());
1203612202 }
1203712203 return left;
1203812204 },
....@@ -12041,7 +12207,7 @@
1204112207 var left = this.unary();
1204212208 var token;
1204312209 while ((token = this.expect('*','/','%'))) {
12044
- left = this.binaryFn(left, token.fn, this.unary());
12210
+ left = this.binaryFn(left, token.text, this.unary());
1204512211 }
1204612212 return left;
1204712213 },
....@@ -12051,9 +12217,9 @@
1205112217 if (this.expect('+')) {
1205212218 return this.primary();
1205312219 } else if ((token = this.expect('-'))) {
12054
- return this.binaryFn(Parser.ZERO, token.fn, this.unary());
12220
+ return this.binaryFn(Parser.ZERO, token.text, this.unary());
1205512221 } else if ((token = this.expect('!'))) {
12056
- return this.unaryFn(token.fn, this.unary());
12222
+ return this.unaryFn(token.text, this.unary());
1205712223 } else {
1205812224 return this.primary();
1205912225 }
....@@ -12061,7 +12227,7 @@
1206112227
1206212228 fieldAccess: function(object) {
1206312229 var expression = this.text;
12064
- var field = this.expect().text;
12230
+ var field = this.consume().text;
1206512231 var getter = getterFn(field, this.options, expression);
1206612232
1206712233 return extend(function $parseFieldAccess(scope, locals, self) {
....@@ -12115,7 +12281,7 @@
1211512281 var args = argsFn.length ? [] : null;
1211612282
1211712283 return function $parseFunctionCall(scope, locals) {
12118
- var context = contextGetter ? contextGetter(scope, locals) : scope;
12284
+ var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope;
1211912285 var fn = fnGetter(scope, locals, context) || noop;
1212012286
1212112287 if (args) {
....@@ -12128,17 +12294,17 @@
1212812294 ensureSafeObject(context, expressionText);
1212912295 ensureSafeFunction(fn, expressionText);
1213012296
12131
- // IE stupidity! (IE doesn't have apply for some native functions)
12297
+ // IE doesn't have apply for some native functions
1213212298 var v = fn.apply
1213312299 ? fn.apply(context, args)
1213412300 : fn(args[0], args[1], args[2], args[3], args[4]);
1213512301
1213612302 return ensureSafeObject(v, expressionText);
12137
- };
12303
+ };
1213812304 },
1213912305
1214012306 // This is used with json array declaration
12141
- arrayDeclaration: function () {
12307
+ arrayDeclaration: function() {
1214212308 var elementFns = [];
1214312309 if (this.peekToken().text !== ']') {
1214412310 do {
....@@ -12146,8 +12312,7 @@
1214612312 // Support trailing commas per ES5.1.
1214712313 break;
1214812314 }
12149
- var elementFn = this.expression();
12150
- elementFns.push(elementFn);
12315
+ elementFns.push(this.expression());
1215112316 } while (this.expect(','));
1215212317 }
1215312318 this.consume(']');
....@@ -12165,7 +12330,7 @@
1216512330 });
1216612331 },
1216712332
12168
- object: function () {
12333
+ object: function() {
1216912334 var keys = [], valueFns = [];
1217012335 if (this.peekToken().text !== '}') {
1217112336 do {
....@@ -12173,11 +12338,16 @@
1217312338 // Support trailing commas per ES5.1.
1217412339 break;
1217512340 }
12176
- var token = this.expect();
12177
- keys.push(token.string || token.text);
12341
+ var token = this.consume();
12342
+ if (token.constant) {
12343
+ keys.push(token.value);
12344
+ } else if (token.identifier) {
12345
+ keys.push(token.text);
12346
+ } else {
12347
+ this.throwError("invalid key", token);
12348
+ }
1217812349 this.consume(':');
12179
- var value = this.expression();
12180
- valueFns.push(value);
12350
+ valueFns.push(this.expression());
1218112351 } while (this.expect(','));
1218212352 }
1218312353 this.consume('}');
....@@ -12220,50 +12390,71 @@
1222012390 return setValue;
1222112391 }
1222212392
12223
-var getterFnCache = createMap();
12393
+var getterFnCacheDefault = createMap();
12394
+var getterFnCacheExpensive = createMap();
12395
+
12396
+function isPossiblyDangerousMemberName(name) {
12397
+ return name == 'constructor';
12398
+}
1222412399
1222512400 /**
1222612401 * Implementation of the "Black Hole" variant from:
1222712402 * - http://jsperf.com/angularjs-parse-getter/4
1222812403 * - http://jsperf.com/path-evaluation-simplified/7
1222912404 */
12230
-function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) {
12405
+function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) {
1223112406 ensureSafeMemberName(key0, fullExp);
1223212407 ensureSafeMemberName(key1, fullExp);
1223312408 ensureSafeMemberName(key2, fullExp);
1223412409 ensureSafeMemberName(key3, fullExp);
1223512410 ensureSafeMemberName(key4, fullExp);
12411
+ var eso = function(o) {
12412
+ return ensureSafeObject(o, fullExp);
12413
+ };
12414
+ var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity;
12415
+ var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity;
12416
+ var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity;
12417
+ var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity;
12418
+ var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity;
1223612419
1223712420 return function cspSafeGetter(scope, locals) {
1223812421 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
1223912422
1224012423 if (pathVal == null) return pathVal;
12241
- pathVal = pathVal[key0];
12424
+ pathVal = eso0(pathVal[key0]);
1224212425
1224312426 if (!key1) return pathVal;
1224412427 if (pathVal == null) return undefined;
12245
- pathVal = pathVal[key1];
12428
+ pathVal = eso1(pathVal[key1]);
1224612429
1224712430 if (!key2) return pathVal;
1224812431 if (pathVal == null) return undefined;
12249
- pathVal = pathVal[key2];
12432
+ pathVal = eso2(pathVal[key2]);
1225012433
1225112434 if (!key3) return pathVal;
1225212435 if (pathVal == null) return undefined;
12253
- pathVal = pathVal[key3];
12436
+ pathVal = eso3(pathVal[key3]);
1225412437
1225512438 if (!key4) return pathVal;
1225612439 if (pathVal == null) return undefined;
12257
- pathVal = pathVal[key4];
12440
+ pathVal = eso4(pathVal[key4]);
1225812441
1225912442 return pathVal;
1226012443 };
1226112444 }
1226212445
12263
-function getterFn(path, options, fullExp) {
12264
- var fn = getterFnCache[path];
12446
+function getterFnWithEnsureSafeObject(fn, fullExpression) {
12447
+ return function(s, l) {
12448
+ return fn(s, l, ensureSafeObject, fullExpression);
12449
+ };
12450
+}
1226512451
12452
+function getterFn(path, options, fullExp) {
12453
+ var expensiveChecks = options.expensiveChecks;
12454
+ var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault);
12455
+ var fn = getterFnCache[path];
1226612456 if (fn) return fn;
12457
+
1226712458
1226812459 var pathKeys = path.split('.'),
1226912460 pathKeysLength = pathKeys.length;
....@@ -12271,13 +12462,13 @@
1227112462 // http://jsperf.com/angularjs-parse-getter/6
1227212463 if (options.csp) {
1227312464 if (pathKeysLength < 6) {
12274
- fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp);
12465
+ fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks);
1227512466 } else {
1227612467 fn = function cspSafeGetter(scope, locals) {
1227712468 var i = 0, val;
1227812469 do {
1227912470 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++],
12280
- pathKeys[i++], fullExp)(scope, locals);
12471
+ pathKeys[i++], fullExp, expensiveChecks)(scope, locals);
1228112472
1228212473 locals = undefined; // clear after first iteration
1228312474 scope = val;
....@@ -12287,22 +12478,33 @@
1228712478 }
1228812479 } else {
1228912480 var code = '';
12481
+ if (expensiveChecks) {
12482
+ code += 's = eso(s, fe);\nl = eso(l, fe);\n';
12483
+ }
12484
+ var needsEnsureSafeObject = expensiveChecks;
1229012485 forEach(pathKeys, function(key, index) {
1229112486 ensureSafeMemberName(key, fullExp);
12292
- code += 'if(s == null) return undefined;\n' +
12293
- 's='+ (index
12487
+ var lookupJs = (index
1229412488 // we simply dereference 's' on any .dot notation
1229512489 ? 's'
1229612490 // but if we are first then we check locals first, and if so read it first
12297
- : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n';
12491
+ : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
12492
+ if (expensiveChecks || isPossiblyDangerousMemberName(key)) {
12493
+ lookupJs = 'eso(' + lookupJs + ', fe)';
12494
+ needsEnsureSafeObject = true;
12495
+ }
12496
+ code += 'if(s == null) return undefined;\n' +
12497
+ 's=' + lookupJs + ';\n';
1229812498 });
1229912499 code += 'return s;';
1230012500
1230112501 /* jshint -W054 */
12302
- var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals
12502
+ var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject
1230312503 /* jshint +W054 */
1230412504 evaledFnGetter.toString = valueFn(code);
12305
-
12505
+ if (needsEnsureSafeObject) {
12506
+ evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp);
12507
+ }
1230612508 fn = evaledFnGetter;
1230712509 }
1230812510
....@@ -12312,6 +12514,12 @@
1231212514 };
1231312515 getterFnCache[path] = fn;
1231412516 return fn;
12517
+}
12518
+
12519
+var objectValueOf = Object.prototype.valueOf;
12520
+
12521
+function getValueOf(value) {
12522
+ return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
1231512523 }
1231612524
1231712525 ///////////////////////////////////
....@@ -12366,15 +12574,20 @@
1236612574 * service.
1236712575 */
1236812576 function $ParseProvider() {
12369
- var cache = createMap();
12577
+ var cacheDefault = createMap();
12578
+ var cacheExpensive = createMap();
1237012579
12371
- var $parseOptions = {
12372
- csp: false
12373
- };
1237412580
1237512581
1237612582 this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
12377
- $parseOptions.csp = $sniffer.csp;
12583
+ var $parseOptions = {
12584
+ csp: $sniffer.csp,
12585
+ expensiveChecks: false
12586
+ },
12587
+ $parseOptionsExpensive = {
12588
+ csp: $sniffer.csp,
12589
+ expensiveChecks: true
12590
+ };
1237812591
1237912592 function wrapSharedExpression(exp) {
1238012593 var wrapped = exp;
....@@ -12391,13 +12604,14 @@
1239112604 return wrapped;
1239212605 }
1239312606
12394
- return function $parse(exp, interceptorFn) {
12607
+ return function $parse(exp, interceptorFn, expensiveChecks) {
1239512608 var parsedExpression, oneTime, cacheKey;
1239612609
1239712610 switch (typeof exp) {
1239812611 case 'string':
1239912612 cacheKey = exp = exp.trim();
1240012613
12614
+ var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
1240112615 parsedExpression = cache[cacheKey];
1240212616
1240312617 if (!parsedExpression) {
....@@ -12406,8 +12620,9 @@
1240612620 exp = exp.substring(2);
1240712621 }
1240812622
12409
- var lexer = new Lexer($parseOptions);
12410
- var parser = new Parser(lexer, $filter, $parseOptions);
12623
+ var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
12624
+ var lexer = new Lexer(parseOptions);
12625
+ var parser = new Parser(lexer, $filter, parseOptions);
1241112626 parsedExpression = parser.parse(exp);
1241212627
1241312628 if (parsedExpression.constant) {
....@@ -12460,7 +12675,7 @@
1246012675 // attempt to convert the value to a primitive type
1246112676 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
1246212677 // be cheaply dirty-checked
12463
- newValue = newValue.valueOf();
12678
+ newValue = getValueOf(newValue);
1246412679
1246512680 if (typeof newValue === 'object') {
1246612681 // objects/arrays are not supported - deep-watching them would be too expensive
....@@ -12487,7 +12702,7 @@
1248712702 var newInputValue = inputExpressions(scope);
1248812703 if (!expressionInputDirtyCheck(newInputValue, oldInputValue)) {
1248912704 lastResult = parsedExpression(scope);
12490
- oldInputValue = newInputValue && newInputValue.valueOf();
12705
+ oldInputValue = newInputValue && getValueOf(newInputValue);
1249112706 }
1249212707 return lastResult;
1249312708 }, listener, objectEquality);
....@@ -12504,7 +12719,7 @@
1250412719 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
1250512720 var newInputValue = inputExpressions[i](scope);
1250612721 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
12507
- oldInputValueOfValues[i] = newInputValue && newInputValue.valueOf();
12722
+ oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
1250812723 }
1250912724 }
1251012725
....@@ -12526,7 +12741,7 @@
1252612741 listener.apply(this, arguments);
1252712742 }
1252812743 if (isDefined(value)) {
12529
- scope.$$postDigest(function () {
12744
+ scope.$$postDigest(function() {
1253012745 if (isDefined(lastValue)) {
1253112746 unwatch();
1253212747 }
....@@ -12545,15 +12760,15 @@
1254512760 listener.call(this, value, old, scope);
1254612761 }
1254712762 if (isAllDefined(value)) {
12548
- scope.$$postDigest(function () {
12549
- if(isAllDefined(lastValue)) unwatch();
12763
+ scope.$$postDigest(function() {
12764
+ if (isAllDefined(lastValue)) unwatch();
1255012765 });
1255112766 }
1255212767 }, objectEquality);
1255312768
1255412769 function isAllDefined(value) {
1255512770 var allDefined = true;
12556
- forEach(value, function (val) {
12771
+ forEach(value, function(val) {
1255712772 if (!isDefined(val)) allDefined = false;
1255812773 });
1255912774 return allDefined;
....@@ -12574,8 +12789,16 @@
1257412789
1257512790 function addInterceptor(parsedExpression, interceptorFn) {
1257612791 if (!interceptorFn) return parsedExpression;
12792
+ var watchDelegate = parsedExpression.$$watchDelegate;
1257712793
12578
- var fn = function interceptedExpression(scope, locals) {
12794
+ var regularWatch =
12795
+ watchDelegate !== oneTimeLiteralWatchDelegate &&
12796
+ watchDelegate !== oneTimeWatchDelegate;
12797
+
12798
+ var fn = regularWatch ? function regularInterceptedExpression(scope, locals) {
12799
+ var value = parsedExpression(scope, locals);
12800
+ return interceptorFn(value, scope, locals);
12801
+ } : function oneTimeInterceptedExpression(scope, locals) {
1257912802 var value = parsedExpression(scope, locals);
1258012803 var result = interceptorFn(value, scope, locals);
1258112804 // we only return the interceptor's result if the
....@@ -12605,7 +12828,11 @@
1260512828 * @requires $rootScope
1260612829 *
1260712830 * @description
12608
- * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
12831
+ * A service that helps you run functions asynchronously, and use their return values (or exceptions)
12832
+ * when they are done processing.
12833
+ *
12834
+ * This is an implementation of promises/deferred objects inspired by
12835
+ * [Kris Kowal's Q](https://github.com/kriskowal/q).
1260912836 *
1261012837 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
1261112838 * implementations, and the other which resembles ES6 promises to some degree.
....@@ -12741,15 +12968,11 @@
1274112968 *
1274212969 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
1274312970 *
12744
- * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise,
12971
+ * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
1274512972 * but to do so without modifying the final value. This is useful to release resources or do some
1274612973 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
1274712974 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
1274812975 * more information.
12749
- *
12750
- * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as
12751
- * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to
12752
- * make your code IE8 and Android 2.x compatible.
1275312976 *
1275412977 * # Chaining promises
1275512978 *
....@@ -12917,7 +13140,7 @@
1291713140 } else {
1291813141 promise.reject(state.value);
1291913142 }
12920
- } catch(e) {
13143
+ } catch (e) {
1292113144 promise.reject(e);
1292213145 exceptionHandler(e);
1292313146 }
....@@ -12967,7 +13190,7 @@
1296713190 this.promise.$$state.status = 1;
1296813191 scheduleProcessQueue(this.promise.$$state);
1296913192 }
12970
- } catch(e) {
13193
+ } catch (e) {
1297113194 fns[1](e);
1297213195 exceptionHandler(e);
1297313196 }
....@@ -12995,7 +13218,7 @@
1299513218 callback = callbacks[i][3];
1299613219 try {
1299713220 result.notify(isFunction(callback) ? callback(progress) : progress);
12998
- } catch(e) {
13221
+ } catch (e) {
1299913222 exceptionHandler(e);
1300013223 }
1300113224 }
....@@ -13060,7 +13283,7 @@
1306013283 var callbackOutput = null;
1306113284 try {
1306213285 if (isFunction(callback)) callbackOutput = callback();
13063
- } catch(e) {
13286
+ } catch (e) {
1306413287 return makePromise(e, false);
1306513288 }
1306613289 if (isPromiseLike(callbackOutput)) {
....@@ -13168,7 +13391,7 @@
1316813391 return $Q;
1316913392 }
1317013393
13171
-function $$RAFProvider(){ //rAF
13394
+function $$RAFProvider() { //rAF
1317213395 this.$get = ['$window', '$timeout', function($window, $timeout) {
1317313396 var requestAnimationFrame = $window.requestAnimationFrame ||
1317413397 $window.webkitRequestAnimationFrame ||
....@@ -13267,7 +13490,7 @@
1326713490 * They also provide an event emission/broadcast and subscription facility. See the
1326813491 * {@link guide/scope developer guide on scopes}.
1326913492 */
13270
-function $RootScopeProvider(){
13493
+function $RootScopeProvider() {
1327113494 var TTL = 10;
1327213495 var $rootScopeMinErr = minErr('$rootScope');
1327313496 var lastDirtyWatch = null;
....@@ -13281,7 +13504,7 @@
1328113504 };
1328213505
1328313506 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
13284
- function( $injector, $exceptionHandler, $parse, $browser) {
13507
+ function($injector, $exceptionHandler, $parse, $browser) {
1328513508
1328613509 /**
1328713510 * @ngdoc type
....@@ -13312,6 +13535,10 @@
1331213535 expect(child.salutation).toEqual('Welcome');
1331313536 expect(parent.salutation).toEqual('Hello');
1331413537 * ```
13538
+ *
13539
+ * When interacting with `Scope` in tests, additional helper methods are available on the
13540
+ * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
13541
+ * details.
1331513542 *
1331613543 *
1331713544 * @param {Object.<string, function()>=} providers Map of service factory which need to be
....@@ -13624,7 +13851,7 @@
1362413851 if (!watchExpressions.length) {
1362513852 // No expressions means we call the listener ASAP
1362613853 var shouldCall = true;
13627
- self.$evalAsync(function () {
13854
+ self.$evalAsync(function() {
1362813855 if (shouldCall) listener(newValues, newValues, self);
1362913856 });
1363013857 return function deregisterWatchGroup() {
....@@ -13641,7 +13868,7 @@
1364113868 });
1364213869 }
1364313870
13644
- forEach(watchExpressions, function (expr, i) {
13871
+ forEach(watchExpressions, function(expr, i) {
1364513872 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
1364613873 newValues[i] = value;
1364713874 oldValues[i] = oldValue;
....@@ -13751,6 +13978,9 @@
1375113978 newValue = _value;
1375213979 var newLength, key, bothNaN, newItem, oldItem;
1375313980
13981
+ // If the new value is undefined, then return undefined as the watch may be a one-time watch
13982
+ if (isUndefined(newValue)) return;
13983
+
1375413984 if (!isObject(newValue)) { // if primitive
1375513985 if (oldValue !== newValue) {
1375613986 oldValue = newValue;
....@@ -13813,7 +14043,7 @@
1381314043 if (oldLength > newLength) {
1381414044 // we used to have more keys, need to find them and destroy them.
1381514045 changeDetected++;
13816
- for(key in oldValue) {
14046
+ for (key in oldValue) {
1381714047 if (!newValue.hasOwnProperty(key)) {
1381814048 oldLength--;
1381914049 delete oldValue[key];
....@@ -13933,7 +14163,7 @@
1393314163 dirty = false;
1393414164 current = target;
1393514165
13936
- while(asyncQueue.length) {
14166
+ while (asyncQueue.length) {
1393714167 try {
1393814168 asyncTask = asyncQueue.shift();
1393914169 asyncTask.scope.$eval(asyncTask.expression);
....@@ -13966,11 +14196,11 @@
1396614196 if (ttl < 5) {
1396714197 logIdx = 4 - ttl;
1396814198 if (!watchLog[logIdx]) watchLog[logIdx] = [];
13969
- logMsg = (isFunction(watch.exp))
13970
- ? 'fn: ' + (watch.exp.name || watch.exp.toString())
13971
- : watch.exp;
13972
- logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
13973
- watchLog[logIdx].push(logMsg);
14199
+ watchLog[logIdx].push({
14200
+ msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
14201
+ newVal: value,
14202
+ oldVal: last
14203
+ });
1397414204 }
1397514205 } else if (watch === lastDirtyWatch) {
1397614206 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
....@@ -13990,7 +14220,7 @@
1399014220 // this piece should be kept in sync with the traversal in $broadcast
1399114221 if (!(next = (current.$$childHead ||
1399214222 (current !== target && current.$$nextSibling)))) {
13993
- while(current !== target && !(next = current.$$nextSibling)) {
14223
+ while (current !== target && !(next = current.$$nextSibling)) {
1399414224 current = current.$parent;
1399514225 }
1399614226 }
....@@ -13998,19 +14228,19 @@
1399814228
1399914229 // `break traverseScopesLoop;` takes us to here
1400014230
14001
- if((dirty || asyncQueue.length) && !(ttl--)) {
14231
+ if ((dirty || asyncQueue.length) && !(ttl--)) {
1400214232 clearPhase();
1400314233 throw $rootScopeMinErr('infdig',
1400414234 '{0} $digest() iterations reached. Aborting!\n' +
1400514235 'Watchers fired in the last 5 iterations: {1}',
14006
- TTL, toJson(watchLog));
14236
+ TTL, watchLog);
1400714237 }
1400814238
1400914239 } while (dirty || asyncQueue.length);
1401014240
1401114241 clearPhase();
1401214242
14013
- while(postDigestQueue.length) {
14243
+ while (postDigestQueue.length) {
1401414244 try {
1401514245 postDigestQueue.shift()();
1401614246 } catch (e) {
....@@ -14166,7 +14396,7 @@
1416614396 asyncQueue.push({scope: this, expression: expr});
1416714397 },
1416814398
14169
- $$postDigest : function(fn) {
14399
+ $$postDigest: function(fn) {
1417014400 postDigestQueue.push(fn);
1417114401 },
1417214402
....@@ -14303,8 +14533,11 @@
1430314533
1430414534 var self = this;
1430514535 return function() {
14306
- namedListeners[namedListeners.indexOf(listener)] = null;
14307
- decrementListenerCount(self, 1, name);
14536
+ var indexOfListener = namedListeners.indexOf(listener);
14537
+ if (indexOfListener !== -1) {
14538
+ namedListeners[indexOfListener] = null;
14539
+ decrementListenerCount(self, 1, name);
14540
+ }
1430814541 };
1430914542 },
1431014543
....@@ -14351,7 +14584,7 @@
1435114584 do {
1435214585 namedListeners = scope.$$listeners[name] || empty;
1435314586 event.currentScope = scope;
14354
- for (i=0, length=namedListeners.length; i<length; i++) {
14587
+ for (i = 0, length = namedListeners.length; i < length; i++) {
1435514588
1435614589 // if listeners were deregistered, defragment the array
1435714590 if (!namedListeners[i]) {
....@@ -14425,7 +14658,7 @@
1442514658 while ((current = next)) {
1442614659 event.currentScope = current;
1442714660 listeners = current.$$listeners[name] || [];
14428
- for (i=0, length = listeners.length; i<length; i++) {
14661
+ for (i = 0, length = listeners.length; i < length; i++) {
1442914662 // if listeners were deregistered, defragment the array
1443014663 if (!listeners[i]) {
1443114664 listeners.splice(i, 1);
....@@ -14436,7 +14669,7 @@
1443614669
1443714670 try {
1443814671 listeners[i].apply(null, listenerArgs);
14439
- } catch(e) {
14672
+ } catch (e) {
1444014673 $exceptionHandler(e);
1444114674 }
1444214675 }
....@@ -14447,7 +14680,7 @@
1444714680 // (though it differs due to having the extra check for $$listenerCount)
1444814681 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
1444914682 (current !== target && current.$$nextSibling)))) {
14450
- while(current !== target && !(next = current.$$nextSibling)) {
14683
+ while (current !== target && !(next = current.$$nextSibling)) {
1445114684 current = current.$parent;
1445214685 }
1445314686 }
....@@ -14501,7 +14734,7 @@
1450114734 while (applyAsyncQueue.length) {
1450214735 try {
1450314736 applyAsyncQueue.shift()();
14504
- } catch(e) {
14737
+ } catch (e) {
1450514738 $exceptionHandler(e);
1450614739 }
1450714740 }
....@@ -14581,7 +14814,7 @@
1458114814 var normalizedVal;
1458214815 normalizedVal = urlResolve(uri).href;
1458314816 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
14584
- return 'unsafe:'+normalizedVal;
14817
+ return 'unsafe:' + normalizedVal;
1458514818 }
1458614819 return uri;
1458714820 };
....@@ -14601,15 +14834,6 @@
1460114834 };
1460214835
1460314836 // Helper functions follow.
14604
-
14605
-// Copied from:
14606
-// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962
14607
-// Prereq: s is a string.
14608
-function escapeForRegexp(s) {
14609
- return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
14610
- replace(/\x08/g, '\\x08');
14611
-}
14612
-
1461314837
1461414838 function adjustMatcher(matcher) {
1461514839 if (matcher === 'self') {
....@@ -14746,7 +14970,7 @@
1474614970 * @description
1474714971 * Sets/Gets the whitelist of trusted resource URLs.
1474814972 */
14749
- this.resourceUrlWhitelist = function (value) {
14973
+ this.resourceUrlWhitelist = function(value) {
1475014974 if (arguments.length) {
1475114975 resourceUrlWhitelist = adjustMatchers(value);
1475214976 }
....@@ -14780,7 +15004,7 @@
1478015004 * Sets/Gets the blacklist of trusted resource URLs.
1478115005 */
1478215006
14783
- this.resourceUrlBlacklist = function (value) {
15007
+ this.resourceUrlBlacklist = function(value) {
1478415008 if (arguments.length) {
1478515009 resourceUrlBlacklist = adjustMatchers(value);
1478615010 }
....@@ -15261,7 +15485,7 @@
1526115485 * @description
1526215486 * Enables/disables SCE and returns the current value.
1526315487 */
15264
- this.enabled = function (value) {
15488
+ this.enabled = function(value) {
1526515489 if (arguments.length) {
1526615490 enabled = !!value;
1526715491 }
....@@ -15315,11 +15539,11 @@
1531515539 * sce.js and sceSpecs.js would need to be aware of this detail.
1531615540 */
1531715541
15318
- this.$get = ['$document', '$parse', '$sceDelegate', function(
15319
- $document, $parse, $sceDelegate) {
15542
+ this.$get = ['$parse', '$sceDelegate', function(
15543
+ $parse, $sceDelegate) {
1532015544 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
1532115545 // the "expression(javascript expression)" syntax which is insecure.
15322
- if (enabled && $document[0].documentMode < 8) {
15546
+ if (enabled && msie < 8) {
1532315547 throw $sceMinErr('iequirks',
1532415548 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
1532515549 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
....@@ -15339,7 +15563,7 @@
1533915563 * @description
1534015564 * Returns a boolean indicating if SCE is enabled.
1534115565 */
15342
- sce.isEnabled = function () {
15566
+ sce.isEnabled = function() {
1534315567 return enabled;
1534415568 };
1534515569 sce.trustAs = $sceDelegate.trustAs;
....@@ -15375,7 +15599,7 @@
1537515599 if (parsed.literal && parsed.constant) {
1537615600 return parsed;
1537715601 } else {
15378
- return $parse(expr, function (value) {
15602
+ return $parse(expr, function(value) {
1537915603 return sce.getTrusted(type, value);
1538015604 });
1538115605 }
....@@ -15628,15 +15852,15 @@
1562815852 getTrusted = sce.getTrusted,
1562915853 trustAs = sce.trustAs;
1563015854
15631
- forEach(SCE_CONTEXTS, function (enumValue, name) {
15855
+ forEach(SCE_CONTEXTS, function(enumValue, name) {
1563215856 var lName = lowercase(name);
15633
- sce[camelCase("parse_as_" + lName)] = function (expr) {
15857
+ sce[camelCase("parse_as_" + lName)] = function(expr) {
1563415858 return parse(enumValue, expr);
1563515859 };
15636
- sce[camelCase("get_trusted_" + lName)] = function (value) {
15860
+ sce[camelCase("get_trusted_" + lName)] = function(value) {
1563715861 return getTrusted(enumValue, value);
1563815862 };
15639
- sce[camelCase("trust_as_" + lName)] = function (value) {
15863
+ sce[camelCase("trust_as_" + lName)] = function(value) {
1564015864 return trustAs(enumValue, value);
1564115865 };
1564215866 });
....@@ -15667,29 +15891,29 @@
1566715891 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
1566815892 document = $document[0] || {},
1566915893 vendorPrefix,
15670
- vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
15894
+ vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
1567115895 bodyStyle = document.body && document.body.style,
1567215896 transitions = false,
1567315897 animations = false,
1567415898 match;
1567515899
1567615900 if (bodyStyle) {
15677
- for(var prop in bodyStyle) {
15678
- if(match = vendorRegex.exec(prop)) {
15901
+ for (var prop in bodyStyle) {
15902
+ if (match = vendorRegex.exec(prop)) {
1567915903 vendorPrefix = match[0];
1568015904 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
1568115905 break;
1568215906 }
1568315907 }
1568415908
15685
- if(!vendorPrefix) {
15909
+ if (!vendorPrefix) {
1568615910 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
1568715911 }
1568815912
1568915913 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
1569015914 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
1569115915
15692
- if (android && (!transitions||!animations)) {
15916
+ if (android && (!transitions || !animations)) {
1569315917 transitions = isString(document.body.style.webkitTransition);
1569415918 animations = isString(document.body.style.webkitAnimation);
1569515919 }
....@@ -15712,7 +15936,9 @@
1571215936 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
1571315937 // it. In particular the event is not fired when backspace or delete key are pressed or
1571415938 // when cut operation is performed.
15715
- if (event == 'input' && msie == 9) return false;
15939
+ // IE10+ implements 'input' event but it erroneously fires under various situations,
15940
+ // e.g. when placeholder changes, or a form is focused.
15941
+ if (event === 'input' && msie <= 11) return false;
1571615942
1571715943 if (isUndefined(eventSupport[event])) {
1571815944 var divElm = document.createElement('div');
....@@ -15723,8 +15949,8 @@
1572315949 },
1572415950 csp: csp(),
1572515951 vendorPrefix: vendorPrefix,
15726
- transitions : transitions,
15727
- animations : animations,
15952
+ transitions: transitions,
15953
+ animations: animations,
1572815954 android: android
1572915955 };
1573015956 }];
....@@ -15755,24 +15981,33 @@
1575515981 var self = handleRequestFn;
1575615982 self.totalPendingRequests++;
1575715983
15758
- return $http.get(tpl, { cache : $templateCache })
15759
- .then(function(response) {
15760
- var html = response.data;
15761
- if(!html || html.length === 0) {
15762
- return handleError();
15763
- }
15984
+ var transformResponse = $http.defaults && $http.defaults.transformResponse;
1576415985
15986
+ if (isArray(transformResponse)) {
15987
+ transformResponse = transformResponse.filter(function(transformer) {
15988
+ return transformer !== defaultHttpResponseTransform;
15989
+ });
15990
+ } else if (transformResponse === defaultHttpResponseTransform) {
15991
+ transformResponse = null;
15992
+ }
15993
+
15994
+ var httpOptions = {
15995
+ cache: $templateCache,
15996
+ transformResponse: transformResponse
15997
+ };
15998
+
15999
+ return $http.get(tpl, httpOptions)
16000
+ .then(function(response) {
1576516001 self.totalPendingRequests--;
15766
- $templateCache.put(tpl, html);
15767
- return html;
16002
+ return response.data;
1576816003 }, handleError);
1576916004
15770
- function handleError() {
16005
+ function handleError(resp) {
1577116006 self.totalPendingRequests--;
1577216007 if (!ignoreRequestError) {
1577316008 throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
1577416009 }
15775
- return $q.reject();
16010
+ return $q.reject(resp);
1577616011 }
1577716012 }
1577816013
....@@ -15815,7 +16050,7 @@
1581516050 if (dataBinding) {
1581616051 forEach(dataBinding, function(bindingName) {
1581716052 if (opt_exactMatch) {
15818
- var matcher = new RegExp('(^|\\s)' + expression + '(\\s|\\||$)');
16053
+ var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
1581916054 if (matcher.test(bindingName)) {
1582016055 matches.push(binding);
1582116056 }
....@@ -15937,7 +16172,7 @@
1593716172 timeoutId = $browser.defer(function() {
1593816173 try {
1593916174 deferred.resolve(fn());
15940
- } catch(e) {
16175
+ } catch (e) {
1594116176 deferred.reject(e);
1594216177 $exceptionHandler(e);
1594316178 }
....@@ -15988,7 +16223,7 @@
1598816223 // exactly the behavior needed here. There is little value is mocking these out for this
1598916224 // service.
1599016225 var urlParsingNode = document.createElement("a");
15991
-var originUrl = urlResolve(window.location.href, true);
16226
+var originUrl = urlResolve(window.location.href);
1599216227
1599316228
1599416229 /**
....@@ -16043,7 +16278,7 @@
1604316278 * | pathname | The pathname, beginning with "/"
1604416279 *
1604516280 */
16046
-function urlResolve(url, base) {
16281
+function urlResolve(url) {
1604716282 var href = url;
1604816283
1604916284 if (msie) {
....@@ -16103,7 +16338,7 @@
1610316338 <file name="index.html">
1610416339 <script>
1610516340 angular.module('windowExample', [])
16106
- .controller('ExampleController', ['$scope', '$window', function ($scope, $window) {
16341
+ .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
1610716342 $scope.greeting = 'Hello, World!';
1610816343 $scope.doGreeting = function(greeting) {
1610916344 $window.alert(greeting);
....@@ -16124,7 +16359,7 @@
1612416359 </file>
1612516360 </example>
1612616361 */
16127
-function $WindowProvider(){
16362
+function $WindowProvider() {
1612816363 this.$get = valueFn(window);
1612916364 }
1613016365
....@@ -16233,7 +16468,7 @@
1623316468 * of the registered filter instances.
1623416469 */
1623516470 function register(name, factory) {
16236
- if(isObject(name)) {
16471
+ if (isObject(name)) {
1623716472 var filters = {};
1623816473 forEach(name, function(filter, key) {
1623916474 filters[key] = register(key, filter);
....@@ -16395,106 +16630,103 @@
1639516630 return function(array, expression, comparator) {
1639616631 if (!isArray(array)) return array;
1639716632
16398
- var comparatorType = typeof(comparator),
16399
- predicates = [];
16633
+ var predicateFn;
16634
+ var matchAgainstAnyProp;
1640016635
16401
- predicates.check = function(value, index) {
16402
- for (var j = 0; j < predicates.length; j++) {
16403
- if(!predicates[j](value, index)) {
16404
- return false;
16405
- }
16406
- }
16407
- return true;
16408
- };
16409
-
16410
- if (comparatorType !== 'function') {
16411
- if (comparatorType === 'boolean' && comparator) {
16412
- comparator = function(obj, text) {
16413
- return angular.equals(obj, text);
16414
- };
16415
- } else {
16416
- comparator = function(obj, text) {
16417
- if (obj && text && typeof obj === 'object' && typeof text === 'object') {
16418
- for (var objKey in obj) {
16419
- if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
16420
- comparator(obj[objKey], text[objKey])) {
16421
- return true;
16422
- }
16423
- }
16424
- return false;
16425
- }
16426
- text = (''+text).toLowerCase();
16427
- return (''+obj).toLowerCase().indexOf(text) > -1;
16428
- };
16429
- }
16430
- }
16431
-
16432
- var search = function(obj, text){
16433
- if (typeof text === 'string' && text.charAt(0) === '!') {
16434
- return !search(obj, text.substr(1));
16435
- }
16436
- switch (typeof obj) {
16437
- case 'boolean':
16438
- case 'number':
16439
- case 'string':
16440
- return comparator(obj, text);
16441
- case 'object':
16442
- switch (typeof text) {
16443
- case 'object':
16444
- return comparator(obj, text);
16445
- default:
16446
- for ( var objKey in obj) {
16447
- if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
16448
- return true;
16449
- }
16450
- }
16451
- break;
16452
- }
16453
- return false;
16454
- case 'array':
16455
- for ( var i = 0; i < obj.length; i++) {
16456
- if (search(obj[i], text)) {
16457
- return true;
16458
- }
16459
- }
16460
- return false;
16461
- default:
16462
- return false;
16463
- }
16464
- };
1646516636 switch (typeof expression) {
16637
+ case 'function':
16638
+ predicateFn = expression;
16639
+ break;
1646616640 case 'boolean':
1646716641 case 'number':
1646816642 case 'string':
16469
- // Set up expression object and fall through
16470
- expression = {$:expression};
16471
- // jshint -W086
16643
+ matchAgainstAnyProp = true;
16644
+ //jshint -W086
1647216645 case 'object':
16473
- // jshint +W086
16474
- for (var key in expression) {
16475
- (function(path) {
16476
- if (typeof expression[path] === 'undefined') return;
16477
- predicates.push(function(value) {
16478
- return search(path == '$' ? value : (value && value[path]), expression[path]);
16479
- });
16480
- })(key);
16481
- }
16482
- break;
16483
- case 'function':
16484
- predicates.push(expression);
16646
+ //jshint +W086
16647
+ predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
1648516648 break;
1648616649 default:
1648716650 return array;
1648816651 }
16489
- var filtered = [];
16490
- for ( var j = 0; j < array.length; j++) {
16491
- var value = array[j];
16492
- if (predicates.check(value, j)) {
16493
- filtered.push(value);
16494
- }
16495
- }
16496
- return filtered;
16652
+
16653
+ return array.filter(predicateFn);
1649716654 };
16655
+}
16656
+
16657
+// Helper functions for `filterFilter`
16658
+function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
16659
+ var predicateFn;
16660
+
16661
+ if (comparator === true) {
16662
+ comparator = equals;
16663
+ } else if (!isFunction(comparator)) {
16664
+ comparator = function(actual, expected) {
16665
+ if (isObject(actual) || isObject(expected)) {
16666
+ // Prevent an object to be considered equal to a string like `'[object'`
16667
+ return false;
16668
+ }
16669
+
16670
+ actual = lowercase('' + actual);
16671
+ expected = lowercase('' + expected);
16672
+ return actual.indexOf(expected) !== -1;
16673
+ };
16674
+ }
16675
+
16676
+ predicateFn = function(item) {
16677
+ return deepCompare(item, expression, comparator, matchAgainstAnyProp);
16678
+ };
16679
+
16680
+ return predicateFn;
16681
+}
16682
+
16683
+function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
16684
+ var actualType = typeof actual;
16685
+ var expectedType = typeof expected;
16686
+
16687
+ if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
16688
+ return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
16689
+ } else if (actualType === 'array') {
16690
+ // In case `actual` is an array, consider it a match
16691
+ // if ANY of it's items matches `expected`
16692
+ return actual.some(function(item) {
16693
+ return deepCompare(item, expected, comparator, matchAgainstAnyProp);
16694
+ });
16695
+ }
16696
+
16697
+ switch (actualType) {
16698
+ case 'object':
16699
+ var key;
16700
+ if (matchAgainstAnyProp) {
16701
+ for (key in actual) {
16702
+ if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
16703
+ return true;
16704
+ }
16705
+ }
16706
+ return false;
16707
+ } else if (expectedType === 'object') {
16708
+ for (key in expected) {
16709
+ var expectedVal = expected[key];
16710
+ if (isFunction(expectedVal)) {
16711
+ continue;
16712
+ }
16713
+
16714
+ var keyIsDollar = key === '$';
16715
+ var actualVal = keyIsDollar ? actual : actual[key];
16716
+ if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
16717
+ return false;
16718
+ }
16719
+ }
16720
+ return true;
16721
+ } else {
16722
+ return comparator(actual, expected);
16723
+ }
16724
+ break;
16725
+ case 'function':
16726
+ return false;
16727
+ default:
16728
+ return comparator(actual, expected);
16729
+ }
1649816730 }
1649916731
1650016732 /**
....@@ -16508,7 +16740,7 @@
1650816740 *
1650916741 * @param {number} amount Input to filter.
1651016742 * @param {string=} symbol Currency symbol or identifier to be displayed.
16511
- * @param {number=} fractionSize Number of decimal places to round the amount to.
16743
+ * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
1651216744 * @returns {string} Formatted number.
1651316745 *
1651416746 *
....@@ -16552,14 +16784,13 @@
1655216784 currencyFilter.$inject = ['$locale'];
1655316785 function currencyFilter($locale) {
1655416786 var formats = $locale.NUMBER_FORMATS;
16555
- return function(amount, currencySymbol, fractionSize){
16787
+ return function(amount, currencySymbol, fractionSize) {
1655616788 if (isUndefined(currencySymbol)) {
1655716789 currencySymbol = formats.CURRENCY_SYM;
1655816790 }
1655916791
1656016792 if (isUndefined(fractionSize)) {
16561
- // TODO: read the default value from the locale file
16562
- fractionSize = 2;
16793
+ fractionSize = formats.PATTERNS[1].maxFrac;
1656316794 }
1656416795
1656516796 // if null or undefined pass it through
....@@ -16648,7 +16879,6 @@
1664816879 if (numStr.indexOf('e') !== -1) {
1664916880 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
1665016881 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
16651
- numStr = '0';
1665216882 number = 0;
1665316883 } else {
1665416884 formatedText = numStr;
....@@ -16669,10 +16899,6 @@
1666916899 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
1667016900 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
1667116901
16672
- if (number === 0) {
16673
- isNegative = false;
16674
- }
16675
-
1667616902 var fraction = ('' + number).split(DECIMAL_SEP);
1667716903 var whole = fraction[0];
1667816904 fraction = fraction[1] || '';
....@@ -16684,7 +16910,7 @@
1668416910 if (whole.length >= (lgroup + group)) {
1668516911 pos = whole.length - lgroup;
1668616912 for (i = 0; i < pos; i++) {
16687
- if ((pos - i)%group === 0 && i !== 0) {
16913
+ if ((pos - i) % group === 0 && i !== 0) {
1668816914 formatedText += groupSep;
1668916915 }
1669016916 formatedText += whole.charAt(i);
....@@ -16692,28 +16918,32 @@
1669216918 }
1669316919
1669416920 for (i = pos; i < whole.length; i++) {
16695
- if ((whole.length - i)%lgroup === 0 && i !== 0) {
16921
+ if ((whole.length - i) % lgroup === 0 && i !== 0) {
1669616922 formatedText += groupSep;
1669716923 }
1669816924 formatedText += whole.charAt(i);
1669916925 }
1670016926
1670116927 // format fraction part.
16702
- while(fraction.length < fractionSize) {
16928
+ while (fraction.length < fractionSize) {
1670316929 fraction += '0';
1670416930 }
1670516931
1670616932 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
1670716933 } else {
16708
-
16709
- if (fractionSize > 0 && number > -1 && number < 1) {
16934
+ if (fractionSize > 0 && number < 1) {
1671016935 formatedText = number.toFixed(fractionSize);
16936
+ number = parseFloat(formatedText);
1671116937 }
1671216938 }
1671316939
16714
- parts.push(isNegative ? pattern.negPre : pattern.posPre);
16715
- parts.push(formatedText);
16716
- parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
16940
+ if (number === 0) {
16941
+ isNegative = false;
16942
+ }
16943
+
16944
+ parts.push(isNegative ? pattern.negPre : pattern.posPre,
16945
+ formatedText,
16946
+ isNegative ? pattern.negSuf : pattern.posSuf);
1671716947 return parts.join('');
1671816948 }
1671916949
....@@ -16724,7 +16954,7 @@
1672416954 num = -num;
1672516955 }
1672616956 num = '' + num;
16727
- while(num.length < digits) num = '0' + num;
16957
+ while (num.length < digits) num = '0' + num;
1672816958 if (trim)
1672916959 num = num.substr(num.length - digits);
1673016960 return neg + num;
....@@ -16737,7 +16967,7 @@
1673716967 var value = date['get' + name]();
1673816968 if (offset > 0 || value > -offset)
1673916969 value += offset;
16740
- if (value === 0 && offset == -12 ) value = 12;
16970
+ if (value === 0 && offset == -12) value = 12;
1674116971 return padNumber(value, size, trim);
1674216972 };
1674316973 }
....@@ -16932,10 +17162,10 @@
1693217162 tzMin = int(match[9] + match[11]);
1693317163 }
1693417164 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
16935
- var h = int(match[4]||0) - tzHour;
16936
- var m = int(match[5]||0) - tzMin;
16937
- var s = int(match[6]||0);
16938
- var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000);
17165
+ var h = int(match[4] || 0) - tzHour;
17166
+ var m = int(match[5] || 0) - tzMin;
17167
+ var s = int(match[6] || 0);
17168
+ var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
1693917169 timeSetter.call(date, h, m, s, ms);
1694017170 return date;
1694117171 }
....@@ -16962,7 +17192,7 @@
1696217192 return date;
1696317193 }
1696417194
16965
- while(format) {
17195
+ while (format) {
1696617196 match = DATE_FORMATS_SPLIT.exec(format);
1696717197 if (match) {
1696817198 parts = concat(parts, match, 1);
....@@ -16977,7 +17207,7 @@
1697717207 date = new Date(date.getTime());
1697817208 date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
1697917209 }
16980
- forEach(parts, function(value){
17210
+ forEach(parts, function(value) {
1698117211 fn = DATE_FORMATS[value];
1698217212 text += fn ? fn(date, $locale.DATETIME_FORMATS)
1698317213 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
....@@ -17000,25 +17230,31 @@
1700017230 * the binding is automatically converted to JSON.
1700117231 *
1700217232 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
17233
+ * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
1700317234 * @returns {string} JSON string.
1700417235 *
1700517236 *
1700617237 * @example
1700717238 <example>
1700817239 <file name="index.html">
17009
- <pre>{{ {'name':'value'} | json }}</pre>
17240
+ <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
17241
+ <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
1701017242 </file>
1701117243 <file name="protractor.js" type="protractor">
1701217244 it('should jsonify filtered objects', function() {
17013
- expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
17245
+ expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
17246
+ expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
1701417247 });
1701517248 </file>
1701617249 </example>
1701717250 *
1701817251 */
1701917252 function jsonFilter() {
17020
- return function(object) {
17021
- return toJson(object, true);
17253
+ return function(object, spacing) {
17254
+ if (isUndefined(spacing)) {
17255
+ spacing = 2;
17256
+ }
17257
+ return toJson(object, spacing);
1702217258 };
1702317259 }
1702417260
....@@ -17130,7 +17366,7 @@
1713017366 </file>
1713117367 </example>
1713217368 */
17133
-function limitToFilter(){
17369
+function limitToFilter() {
1713417370 return function(input, limit) {
1713517371 if (isNumber(input)) input = input.toString();
1713617372 if (!isArray(input) && !isString(input)) return input;
....@@ -17167,7 +17403,7 @@
1716717403 n = input.length;
1716817404 }
1716917405
17170
- for (; i<n; i++) {
17406
+ for (; i < n; i++) {
1717117407 out.push(input[i]);
1717217408 }
1717317409
....@@ -17291,42 +17527,40 @@
1729117527 </example>
1729217528 */
1729317529 orderByFilter.$inject = ['$parse'];
17294
-function orderByFilter($parse){
17530
+function orderByFilter($parse) {
1729517531 return function(array, sortPredicate, reverseOrder) {
1729617532 if (!(isArrayLike(array))) return array;
17297
- sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
17533
+ sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate];
1729817534 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
17299
- sortPredicate = sortPredicate.map(function(predicate){
17535
+ sortPredicate = sortPredicate.map(function(predicate) {
1730017536 var descending = false, get = predicate || identity;
1730117537 if (isString(predicate)) {
1730217538 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
1730317539 descending = predicate.charAt(0) == '-';
1730417540 predicate = predicate.substring(1);
1730517541 }
17306
- if ( predicate === '' ) {
17542
+ if (predicate === '') {
1730717543 // Effectively no predicate was passed so we compare identity
17308
- return reverseComparator(function(a,b) {
17544
+ return reverseComparator(function(a, b) {
1730917545 return compare(a, b);
1731017546 }, descending);
1731117547 }
1731217548 get = $parse(predicate);
1731317549 if (get.constant) {
1731417550 var key = get();
17315
- return reverseComparator(function(a,b) {
17551
+ return reverseComparator(function(a, b) {
1731617552 return compare(a[key], b[key]);
1731717553 }, descending);
1731817554 }
1731917555 }
17320
- return reverseComparator(function(a,b){
17556
+ return reverseComparator(function(a, b) {
1732117557 return compare(get(a),get(b));
1732217558 }, descending);
1732317559 });
17324
- var arrayCopy = [];
17325
- for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
17326
- return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
17560
+ return slice.call(array).sort(reverseComparator(comparator, reverseOrder));
1732717561
17328
- function comparator(o1, o2){
17329
- for ( var i = 0; i < sortPredicate.length; i++) {
17562
+ function comparator(o1, o2) {
17563
+ for (var i = 0; i < sortPredicate.length; i++) {
1733017564 var comp = sortPredicate[i](o1, o2);
1733117565 if (comp !== 0) return comp;
1733217566 }
....@@ -17334,18 +17568,35 @@
1733417568 }
1733517569 function reverseComparator(comp, descending) {
1733617570 return descending
17337
- ? function(a,b){return comp(b,a);}
17571
+ ? function(a, b) {return comp(b,a);}
1733817572 : comp;
1733917573 }
17340
- function compare(v1, v2){
17574
+ function compare(v1, v2) {
1734117575 var t1 = typeof v1;
1734217576 var t2 = typeof v2;
17343
- if (t1 == t2) {
17344
- if (isDate(v1) && isDate(v2)) {
17345
- v1 = v1.valueOf();
17346
- v2 = v2.valueOf();
17577
+ // Prepare values for Abstract Relational Comparison
17578
+ // (http://www.ecma-international.org/ecma-262/5.1/#sec-11.8.5):
17579
+ // If the resulting values are identical, return 0 to prevent
17580
+ // incorrect re-ordering.
17581
+ if (t1 === t2 && t1 === "object") {
17582
+ // If types are both numbers, emulate abstract ToPrimitive() operation
17583
+ // in order to get primitive values suitable for comparison
17584
+ t1 = typeof (v1.valueOf ? v1 = v1.valueOf() : v1);
17585
+ t2 = typeof (v2.valueOf ? v2 = v2.valueOf() : v2);
17586
+ if (t1 === t2 && t1 === "object") {
17587
+ // Object.prototype.valueOf will return the original object, by
17588
+ // default. If we do not receive a primitive value, use ToString()
17589
+ // instead.
17590
+ t1 = typeof (v1.toString ? v1 = v1.toString() : v1);
17591
+ t2 = typeof (v2.toString ? v2 = v2.toString() : v2);
17592
+
17593
+ // If the end result of toString() for each item is the same, do not
17594
+ // perform relational comparison, and do not re-order objects.
17595
+ if (t1 === t2 && v1 === v2 || t1 === "object") return 0;
1734717596 }
17348
- if (t1 == "string") {
17597
+ }
17598
+ if (t1 === t2) {
17599
+ if (t1 === "string") {
1734917600 v1 = v1.toLowerCase();
1735017601 v2 = v2.toLowerCase();
1735117602 }
....@@ -17389,7 +17640,7 @@
1738917640 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
1739017641 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
1739117642 'xlink:href' : 'href';
17392
- element.on('click', function(event){
17643
+ element.on('click', function(event) {
1739317644 // if we have no href url, then don't navigate anywhere.
1739417645 if (!element.attr(href)) {
1739517646 event.preventDefault();
....@@ -17411,9 +17662,8 @@
1741117662 * make the link go to the wrong URL if the user clicks it before
1741217663 * Angular has a chance to replace the `{{hash}}` markup with its
1741317664 * value. Until Angular replaces the markup the link will be broken
17414
- * and will most likely return a 404 error.
17415
- *
17416
- * The `ngHref` directive solves this problem.
17665
+ * and will most likely return a 404 error. The `ngHref` directive
17666
+ * solves this problem.
1741717667 *
1741817668 * The wrong way to write it:
1741917669 * ```html
....@@ -17868,6 +18118,11 @@
1786818118 * - `pattern`
1786918119 * - `required`
1787018120 * - `url`
18121
+ * - `date`
18122
+ * - `datetimelocal`
18123
+ * - `time`
18124
+ * - `week`
18125
+ * - `month`
1787118126 *
1787218127 * @description
1787318128 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
....@@ -18056,7 +18311,7 @@
1805618311 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
1805718312 * saving or resetting it.
1805818313 */
18059
- form.$setPristine = function () {
18314
+ form.$setPristine = function() {
1806018315 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
1806118316 form.$dirty = false;
1806218317 form.$pristine = true;
....@@ -18079,7 +18334,7 @@
1807918334 * Setting a form controls back to their untouched state is often useful when setting the form
1808018335 * back to its pristine state.
1808118336 */
18082
- form.$setUntouched = function () {
18337
+ form.$setUntouched = function() {
1808318338 forEach(controls, function(control) {
1808418339 control.$setUntouched();
1808518340 });
....@@ -18092,7 +18347,7 @@
1809218347 * @description
1809318348 * Sets the form to its submitted state.
1809418349 */
18095
- form.$setSubmitted = function () {
18350
+ form.$setSubmitted = function() {
1809618351 $animate.addClass(element, SUBMITTED_CLASS);
1809718352 form.$submitted = true;
1809818353 parentForm.$setSubmitted();
....@@ -18290,9 +18545,7 @@
1829018545 controller.$setSubmitted();
1829118546 });
1829218547
18293
- event.preventDefault
18294
- ? event.preventDefault()
18295
- : event.returnValue = false; // IE
18548
+ event.preventDefault();
1829618549 };
1829718550
1829818551 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
....@@ -18369,7 +18622,6 @@
1836918622 * @description
1837018623 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
1837118624 *
18372
- * *NOTE* Not every feature offered is available for all input types.
1837318625 *
1837418626 * @param {string} ngModel Assignable angular expression to data-bind to.
1837518627 * @param {string=} name Property name of the form under which the control is published.
....@@ -18380,10 +18632,16 @@
1838018632 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
1838118633 * minlength.
1838218634 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
18383
- * maxlength.
18384
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
18385
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
18386
- * patterns defined as scope expressions.
18635
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
18636
+ * any length.
18637
+ * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
18638
+ * that contains the regular expression body that will be converted to a regular expression
18639
+ * as in the ngPattern directive.
18640
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
18641
+ * a RegExp found by evaluating the Angular expression given in the attribute value.
18642
+ * If the expression evaluates to a RegExp object then this is used directly.
18643
+ * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
18644
+ * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
1838718645 * @param {string=} ngChange Angular expression to be executed when input changes due to user
1838818646 * interaction with the input element.
1838918647 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
....@@ -18453,7 +18711,10 @@
1845318711 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
1845418712 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
1845518713 * modern browsers do not yet support this input type, it is important to provide cues to users on the
18456
- * expected input format via a placeholder or label. The model must always be a Date object.
18714
+ * expected input format via a placeholder or label.
18715
+ *
18716
+ * The model must always be a Date object, otherwise Angular will throw an error.
18717
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
1845718718 *
1845818719 * The timezone to be used to read/write the `Date` instance in the model can be defined using
1845918720 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
....@@ -18536,12 +18797,15 @@
1853618797
1853718798 /**
1853818799 * @ngdoc input
18539
- * @name input[dateTimeLocal]
18800
+ * @name input[datetime-local]
1854018801 *
1854118802 * @description
1854218803 * Input with datetime validation and transformation. In browsers that do not yet support
1854318804 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
18544
- * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. The model must be a Date object.
18805
+ * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
18806
+ *
18807
+ * The model must always be a Date object, otherwise Angular will throw an error.
18808
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
1854518809 *
1854618810 * The timezone to be used to read/write the `Date` instance in the model can be defined using
1854718811 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
....@@ -18632,6 +18896,9 @@
1863218896 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
1863318897 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
1863418898 *
18899
+ * The model must always be a Date object, otherwise Angular will throw an error.
18900
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
18901
+ *
1863518902 * The timezone to be used to read/write the `Date` instance in the model can be defined using
1863618903 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
1863718904 *
....@@ -18718,7 +18985,10 @@
1871818985 * @description
1871918986 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
1872018987 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
18721
- * week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object.
18988
+ * week format (yyyy-W##), for example: `2013-W02`.
18989
+ *
18990
+ * The model must always be a Date object, otherwise Angular will throw an error.
18991
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
1872218992 *
1872318993 * The timezone to be used to read/write the `Date` instance in the model can be defined using
1872418994 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
....@@ -18804,8 +19074,12 @@
1880419074 * @description
1880519075 * Input with month validation and transformation. In browsers that do not yet support
1880619076 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
18807
- * month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is
18808
- * not set to the first of the month, the first of that model's month is assumed.
19077
+ * month format (yyyy-MM), for example: `2009-01`.
19078
+ *
19079
+ * The model must always be a Date object, otherwise Angular will throw an error.
19080
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
19081
+ * If the model is not set to the first of the month, the next view to model update will set it
19082
+ * to the first of the month.
1880919083 *
1881019084 * The timezone to be used to read/write the `Date` instance in the model can be defined using
1881119085 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
....@@ -18894,6 +19168,8 @@
1889419168 * Text input with number validation and transformation. Sets the `number` validation
1889519169 * error if not a valid number.
1889619170 *
19171
+ * The model must always be a number, otherwise Angular will throw an error.
19172
+ *
1889719173 * @param {string} ngModel Assignable angular expression to data-bind to.
1889819174 * @param {string=} name Property name of the form under which the control is published.
1889919175 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
....@@ -18905,10 +19181,16 @@
1890519181 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
1890619182 * minlength.
1890719183 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
18908
- * maxlength.
18909
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
18910
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
18911
- * patterns defined as scope expressions.
19184
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
19185
+ * any length.
19186
+ * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
19187
+ * that contains the regular expression body that will be converted to a regular expression
19188
+ * as in the ngPattern directive.
19189
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
19190
+ * a RegExp found by evaluating the Angular expression given in the attribute value.
19191
+ * If the expression evaluates to a RegExp object then this is used directly.
19192
+ * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
19193
+ * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
1891219194 * @param {string=} ngChange Angular expression to be executed when input changes due to user
1891319195 * interaction with the input element.
1891419196 *
....@@ -18972,6 +19254,12 @@
1897219254 * Text input with URL validation. Sets the `url` validation error key if the content is not a
1897319255 * valid URL.
1897419256 *
19257
+ * <div class="alert alert-warning">
19258
+ * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
19259
+ * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
19260
+ * the built-in validators (see the {@link guide/forms Forms guide})
19261
+ * </div>
19262
+ *
1897519263 * @param {string} ngModel Assignable angular expression to data-bind to.
1897619264 * @param {string=} name Property name of the form under which the control is published.
1897719265 * @param {string=} required Sets `required` validation error key if the value is not entered.
....@@ -18981,10 +19269,16 @@
1898119269 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
1898219270 * minlength.
1898319271 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
18984
- * maxlength.
18985
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
18986
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
18987
- * patterns defined as scope expressions.
19272
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
19273
+ * any length.
19274
+ * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
19275
+ * that contains the regular expression body that will be converted to a regular expression
19276
+ * as in the ngPattern directive.
19277
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
19278
+ * a RegExp found by evaluating the Angular expression given in the attribute value.
19279
+ * If the expression evaluates to a RegExp object then this is used directly.
19280
+ * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
19281
+ * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
1898819282 * @param {string=} ngChange Angular expression to be executed when input changes due to user
1898919283 * interaction with the input element.
1899019284 *
....@@ -19049,6 +19343,12 @@
1904919343 * Text input with email validation. Sets the `email` validation error key if not a valid email
1905019344 * address.
1905119345 *
19346
+ * <div class="alert alert-warning">
19347
+ * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
19348
+ * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
19349
+ * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
19350
+ * </div>
19351
+ *
1905219352 * @param {string} ngModel Assignable angular expression to data-bind to.
1905319353 * @param {string=} name Property name of the form under which the control is published.
1905419354 * @param {string=} required Sets `required` validation error key if the value is not entered.
....@@ -19058,10 +19358,16 @@
1905819358 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
1905919359 * minlength.
1906019360 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
19061
- * maxlength.
19062
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
19063
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
19064
- * patterns defined as scope expressions.
19361
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
19362
+ * any length.
19363
+ * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
19364
+ * that contains the regular expression body that will be converted to a regular expression
19365
+ * as in the ngPattern directive.
19366
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
19367
+ * a RegExp found by evaluating the Angular expression given in the attribute value.
19368
+ * If the expression evaluates to a RegExp object then this is used directly.
19369
+ * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
19370
+ * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
1906519371 * @param {string=} ngChange Angular expression to be executed when input changes due to user
1906619372 * interaction with the input element.
1906719373 *
....@@ -19227,19 +19533,6 @@
1922719533 'file': noop
1922819534 };
1922919535
19230
-function testFlags(validity, flags) {
19231
- var i, flag;
19232
- if (flags) {
19233
- for (i=0; i<flags.length; ++i) {
19234
- flag = flags[i];
19235
- if (validity[flag]) {
19236
- return true;
19237
- }
19238
- }
19239
- }
19240
- return false;
19241
-}
19242
-
1924319536 function stringBasedInputType(ctrl) {
1924419537 ctrl.$formatters.push(function(value) {
1924519538 return ctrl.$isEmpty(value) ? value : value.toString();
....@@ -19252,8 +19545,6 @@
1925219545 }
1925319546
1925419547 function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
19255
- var validity = element.prop(VALIDITY_STATE_PROPERTY);
19256
- var placeholder = element[0].placeholder, noevent = {};
1925719548 var type = lowercase(element[0].type);
1925819549
1925919550 // In composition mode, users are still inputing intermediate text buffer,
....@@ -19273,18 +19564,13 @@
1927319564 }
1927419565
1927519566 var listener = function(ev) {
19567
+ if (timeout) {
19568
+ $browser.defer.cancel(timeout);
19569
+ timeout = null;
19570
+ }
1927619571 if (composing) return;
1927719572 var value = element.val(),
1927819573 event = ev && ev.type;
19279
-
19280
- // IE (11 and under) seem to emit an 'input' event if the placeholder value changes.
19281
- // We don't want to dirty the value when this happens, so we abort here. Unfortunately,
19282
- // IE also sends input events for other non-input-related things, (such as focusing on a
19283
- // form control), so this change is not entirely enough to solve this.
19284
- if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) {
19285
- placeholder = element[0].placeholder;
19286
- return;
19287
- }
1928819574
1928919575 // By default we will trim the value
1929019576 // If the attribute ng-trim exists we will avoid trimming
....@@ -19308,11 +19594,13 @@
1930819594 } else {
1930919595 var timeout;
1931019596
19311
- var deferListener = function(ev) {
19597
+ var deferListener = function(ev, input, origValue) {
1931219598 if (!timeout) {
1931319599 timeout = $browser.defer(function() {
19314
- listener(ev);
1931519600 timeout = null;
19601
+ if (!input || input.value !== origValue) {
19602
+ listener(ev);
19603
+ }
1931619604 });
1931719605 }
1931819606 };
....@@ -19324,7 +19612,7 @@
1932419612 // command modifiers arrows
1932519613 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
1932619614
19327
- deferListener(event);
19615
+ deferListener(event, this, this.value);
1932819616 });
1932919617
1933019618 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
....@@ -19338,7 +19626,7 @@
1933819626 element.on('change', listener);
1933919627
1934019628 ctrl.$render = function() {
19341
- element.val(ctrl.$isEmpty(ctrl.$modelValue) ? '' : ctrl.$viewValue);
19629
+ element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
1934219630 };
1934319631 }
1934419632
....@@ -19386,8 +19674,8 @@
1938619674 // When a date is JSON'ified to wraps itself inside of an extra
1938719675 // set of double quotes. This makes the date parsing code unable
1938819676 // to match the date string and parse it as a date.
19389
- if (iso.charAt(0) == '"' && iso.charAt(iso.length-1) == '"') {
19390
- iso = iso.substring(1, iso.length-1);
19677
+ if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
19678
+ iso = iso.substring(1, iso.length - 1);
1939119679 }
1939219680 if (ISO_DATE_REGEXP.test(iso)) {
1939319681 return new Date(iso);
....@@ -19448,10 +19736,10 @@
1944819736 });
1944919737
1945019738 ctrl.$formatters.push(function(value) {
19451
- if (!ctrl.$isEmpty(value)) {
19452
- if (!isDate(value)) {
19453
- throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
19454
- }
19739
+ if (value && !isDate(value)) {
19740
+ throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
19741
+ }
19742
+ if (isValidDate(value)) {
1945519743 previousDate = value;
1945619744 if (previousDate && timezone === 'UTC') {
1945719745 var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
....@@ -19460,14 +19748,14 @@
1946019748 return $filter('date')(value, format, timezone);
1946119749 } else {
1946219750 previousDate = null;
19751
+ return '';
1946319752 }
19464
- return '';
1946519753 });
1946619754
1946719755 if (isDefined(attr.min) || attr.ngMin) {
1946819756 var minVal;
1946919757 ctrl.$validators.min = function(value) {
19470
- return ctrl.$isEmpty(value) || isUndefined(minVal) || parseDate(value) >= minVal;
19758
+ return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
1947119759 };
1947219760 attr.$observe('min', function(val) {
1947319761 minVal = parseObservedDateValue(val);
....@@ -19478,18 +19766,18 @@
1947819766 if (isDefined(attr.max) || attr.ngMax) {
1947919767 var maxVal;
1948019768 ctrl.$validators.max = function(value) {
19481
- return ctrl.$isEmpty(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
19769
+ return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
1948219770 };
1948319771 attr.$observe('max', function(val) {
1948419772 maxVal = parseObservedDateValue(val);
1948519773 ctrl.$validate();
1948619774 });
1948719775 }
19488
- // Override the standard $isEmpty to detect invalid dates as well
19489
- ctrl.$isEmpty = function(value) {
19776
+
19777
+ function isValidDate(value) {
1949019778 // Invalid Date: getTime() returns NaN
19491
- return !value || (value.getTime && value.getTime() !== value.getTime());
19492
- };
19779
+ return value && !(value.getTime && value.getTime() !== value.getTime());
19780
+ }
1949319781
1949419782 function parseObservedDateValue(val) {
1949519783 return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
....@@ -19573,7 +19861,8 @@
1957319861 stringBasedInputType(ctrl);
1957419862
1957519863 ctrl.$$parserName = 'url';
19576
- ctrl.$validators.url = function(value) {
19864
+ ctrl.$validators.url = function(modelValue, viewValue) {
19865
+ var value = modelValue || viewValue;
1957719866 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
1957819867 };
1957919868 }
....@@ -19585,7 +19874,8 @@
1958519874 stringBasedInputType(ctrl);
1958619875
1958719876 ctrl.$$parserName = 'email';
19588
- ctrl.$validators.email = function(value) {
19877
+ ctrl.$validators.email = function(modelValue, viewValue) {
19878
+ var value = modelValue || viewValue;
1958919879 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
1959019880 };
1959119881 }
....@@ -19639,9 +19929,11 @@
1963919929 element[0].checked = ctrl.$viewValue;
1964019930 };
1964119931
19642
- // Override the standard `$isEmpty` because an empty checkbox is never equal to the trueValue
19932
+ // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
19933
+ // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
19934
+ // it to a boolean.
1964319935 ctrl.$isEmpty = function(value) {
19644
- return value !== trueValue;
19936
+ return value === false;
1964519937 };
1964619938
1964719939 ctrl.$formatters.push(function(value) {
....@@ -19673,7 +19965,8 @@
1967319965 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
1967419966 * minlength.
1967519967 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
19676
- * maxlength.
19968
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
19969
+ * length.
1967719970 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
1967819971 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
1967919972 * patterns defined as scope expressions.
....@@ -19689,10 +19982,14 @@
1968919982 * @restrict E
1969019983 *
1969119984 * @description
19692
- * HTML input element control with angular data-binding. Input control follows HTML5 input types
19693
- * and polyfills the HTML5 validation behavior for older browsers.
19985
+ * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
19986
+ * input state control, and validation.
19987
+ * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
1969419988 *
19695
- * *NOTE* Not every feature offered is available for all input types.
19989
+ * <div class="alert alert-warning">
19990
+ * **Note:** Not every feature offered is available for all input types.
19991
+ * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
19992
+ * </div>
1969619993 *
1969719994 * @param {string} ngModel Assignable angular expression to data-bind to.
1969819995 * @param {string=} name Property name of the form under which the control is published.
....@@ -19701,7 +19998,8 @@
1970119998 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
1970219999 * minlength.
1970320000 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
19704
- * maxlength.
20001
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
20002
+ * length.
1970520003 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
1970620004 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
1970720005 * patterns defined as scope expressions.
....@@ -19828,19 +20126,25 @@
1982820126 * @name ngModel.NgModelController
1982920127 *
1983020128 * @property {string} $viewValue Actual string value in the view.
19831
- * @property {*} $modelValue The value in the model, that the control is bound to.
20129
+ * @property {*} $modelValue The value in the model that the control is bound to.
1983220130 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
19833
- the control reads value from the DOM. Each function is called, in turn, passing the value
19834
- through to the next. The last return value is used to populate the model.
19835
- Used to sanitize / convert the value as well as validation. For validation,
19836
- the parsers should update the validity state using
19837
- {@link ngModel.NgModelController#$setValidity $setValidity()},
19838
- and return `undefined` for invalid values.
20131
+ the control reads value from the DOM. The functions are called in array order, each passing
20132
+ its return value through to the next. The last return value is forwarded to the
20133
+ {@link ngModel.NgModelController#$validators `$validators`} collection.
20134
+
20135
+Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
20136
+`$viewValue`}.
20137
+
20138
+Returning `undefined` from a parser means a parse error occurred. In that case,
20139
+no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
20140
+will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
20141
+is set to `true`. The parse error is stored in `ngModel.$error.parse`.
1983920142
1984020143 *
1984120144 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
19842
- the model value changes. Each function is called, in turn, passing the value through to the
19843
- next. Used to format / convert values for display in the control and validation.
20145
+ the model value changes. The functions are called in reverse array order, each passing the value through to the
20146
+ next. The last return value is used as the actual DOM value.
20147
+ Used to format / convert values for display in the control.
1984420148 * ```js
1984520149 * function formatter(value) {
1984620150 * if (value) {
....@@ -19871,8 +20175,9 @@
1987120175 * is expected to return a promise when it is run during the model validation process. Once the promise
1987220176 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
1987320177 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
19874
- * value will only be updated once all validators have been fulfilled. Also, keep in mind that all
19875
- * asynchronous validators will only run once all synchronous validators have passed.
20178
+ * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
20179
+ * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
20180
+ * will only run once all synchronous validators have passed.
1987620181 *
1987720182 * Please note that if $http is used then it is important that the server returns a success HTTP response code
1987820183 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
....@@ -19893,9 +20198,6 @@
1989320198 * };
1989420199 * ```
1989520200 *
19896
- * @param {string} name The name of the validator.
19897
- * @param {Function} validationFn The validation function that will be run.
19898
- *
1989920201 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
1990020202 * view value has changed. It is called with no arguments, and its return value is ignored.
1990120203 * This can be used in place of additional $watches against the model value.
....@@ -19909,16 +20211,22 @@
1990920211 * @property {boolean} $dirty True if user has already interacted with the control.
1991020212 * @property {boolean} $valid True if there is no error.
1991120213 * @property {boolean} $invalid True if at least one error on the control.
20214
+ * @property {string} $name The name attribute of the control.
1991220215 *
1991320216 * @description
1991420217 *
19915
- * `NgModelController` provides API for the `ng-model` directive. The controller contains
19916
- * services for data-binding, validation, CSS updates, and value formatting and parsing. It
19917
- * purposefully does not contain any logic which deals with DOM rendering or listening to
19918
- * DOM events. Such DOM related logic should be provided by other directives which make use of
19919
- * `NgModelController` for data-binding.
20218
+ * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
20219
+ * The controller contains services for data-binding, validation, CSS updates, and value formatting
20220
+ * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
20221
+ * listening to DOM events.
20222
+ * Such DOM related logic should be provided by other directives which make use of
20223
+ * `NgModelController` for data-binding to control elements.
20224
+ * Angular provides this DOM logic for most {@link input `input`} elements.
20225
+ * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
20226
+ * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
1992020227 *
19921
- * ## Custom Control Example
20228
+ * @example
20229
+ * ### Custom Control Example
1992220230 * This example shows how to use `NgModelController` with a custom control to achieve
1992320231 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
1992420232 * collaborate together to achieve the desired result.
....@@ -19928,7 +20236,7 @@
1992820236 *
1992920237 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
1993020238 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
19931
- * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks
20239
+ * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
1993220240 * that content using the `$sce` service.
1993320241 *
1993420242 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
....@@ -19960,7 +20268,7 @@
1996020268
1996120269 // Listen for change events to enable binding
1996220270 element.on('blur keyup change', function() {
19963
- scope.$apply(read);
20271
+ scope.$evalAsync(read);
1996420272 });
1996520273 read(); // initialize
1996620274
....@@ -20015,6 +20323,7 @@
2001520323 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
2001620324 this.$viewValue = Number.NaN;
2001720325 this.$modelValue = Number.NaN;
20326
+ this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
2001820327 this.$validators = {};
2001920328 this.$asyncValidators = {};
2002020329 this.$parsers = [];
....@@ -20033,32 +20342,33 @@
2003320342
2003420343
2003520344 var parsedNgModel = $parse($attr.ngModel),
20345
+ parsedNgModelAssign = parsedNgModel.assign,
20346
+ ngModelGet = parsedNgModel,
20347
+ ngModelSet = parsedNgModelAssign,
2003620348 pendingDebounce = null,
2003720349 ctrl = this;
2003820350
20039
- var ngModelGet = function ngModelGet() {
20040
- var modelValue = parsedNgModel($scope);
20041
- if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) {
20042
- modelValue = modelValue();
20043
- }
20044
- return modelValue;
20045
- };
20046
-
20047
- var ngModelSet = function ngModelSet(newValue) {
20048
- var getterSetter;
20049
- if (ctrl.$options && ctrl.$options.getterSetter &&
20050
- isFunction(getterSetter = parsedNgModel($scope))) {
20051
-
20052
- getterSetter(ctrl.$modelValue);
20053
- } else {
20054
- parsedNgModel.assign($scope, ctrl.$modelValue);
20055
- }
20056
- };
20057
-
2005820351 this.$$setOptions = function(options) {
2005920352 ctrl.$options = options;
20353
+ if (options && options.getterSetter) {
20354
+ var invokeModelGetter = $parse($attr.ngModel + '()'),
20355
+ invokeModelSetter = $parse($attr.ngModel + '($$$p)');
2006020356
20061
- if (!parsedNgModel.assign && (!options || !options.getterSetter)) {
20357
+ ngModelGet = function($scope) {
20358
+ var modelValue = parsedNgModel($scope);
20359
+ if (isFunction(modelValue)) {
20360
+ modelValue = invokeModelGetter($scope);
20361
+ }
20362
+ return modelValue;
20363
+ };
20364
+ ngModelSet = function($scope, newValue) {
20365
+ if (isFunction(parsedNgModel($scope))) {
20366
+ invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
20367
+ } else {
20368
+ parsedNgModelAssign($scope, ctrl.$modelValue);
20369
+ }
20370
+ };
20371
+ } else if (!parsedNgModel.assign) {
2006220372 throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
2006320373 $attr.ngModel, startingTag($element));
2006420374 }
....@@ -20091,17 +20401,18 @@
2009120401 * @name ngModel.NgModelController#$isEmpty
2009220402 *
2009320403 * @description
20094
- * This is called when we need to determine if the value of the input is empty.
20404
+ * This is called when we need to determine if the value of an input is empty.
2009520405 *
2009620406 * For instance, the required directive does this to work out if the input has data or not.
20407
+ *
2009720408 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
2009820409 *
2009920410 * You can override this for input directives whose concept of being empty is different to the
2010020411 * default. The `checkboxInputType` directive does this because in its case a value of `false`
2010120412 * implies empty.
2010220413 *
20103
- * @param {*} value Model value to check.
20104
- * @returns {boolean} True if `value` is empty.
20414
+ * @param {*} value The value of the input to check for emptiness.
20415
+ * @returns {boolean} True if `value` is "empty".
2010520416 */
2010620417 this.$isEmpty = function(value) {
2010720418 return isUndefined(value) || value === '' || value === null || value !== value;
....@@ -20115,19 +20426,22 @@
2011520426 * @name ngModel.NgModelController#$setValidity
2011620427 *
2011720428 * @description
20118
- * Change the validity state, and notifies the form.
20429
+ * Change the validity state, and notify the form.
2011920430 *
20120
- * This method can be called within $parsers/$formatters. However, if possible, please use the
20121
- * `ngModel.$validators` pipeline which is designed to call this method automatically.
20431
+ * This method can be called within $parsers/$formatters or a custom validation implementation.
20432
+ * However, in most cases it should be sufficient to use the `ngModel.$validators` and
20433
+ * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
2012220434 *
20123
- * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
20124
- * to `$error[validationErrorKey]` and `$pending[validationErrorKey]`
20125
- * so that it is available for data-binding.
20435
+ * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
20436
+ * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
20437
+ * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
2012620438 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
2012720439 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
2012820440 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
2012920441 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
20130
- * or skipped (null).
20442
+ * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
20443
+ * Skipped is used by Angular when validators do not run because of parse errors and
20444
+ * when `$asyncValidators` do not run because any of the `$validators` failed.
2013120445 */
2013220446 addSetValidityMethod({
2013320447 ctrl: this,
....@@ -20149,15 +20463,34 @@
2014920463 * @description
2015020464 * Sets the control to its pristine state.
2015120465 *
20152
- * This method can be called to remove the 'ng-dirty' class and set the control to its pristine
20153
- * state (ng-pristine class). A model is considered to be pristine when the model has not been changed
20154
- * from when first compiled within then form.
20466
+ * This method can be called to remove the `ng-dirty` class and set the control to its pristine
20467
+ * state (`ng-pristine` class). A model is considered to be pristine when the control
20468
+ * has not been changed from when first compiled.
2015520469 */
20156
- this.$setPristine = function () {
20470
+ this.$setPristine = function() {
2015720471 ctrl.$dirty = false;
2015820472 ctrl.$pristine = true;
2015920473 $animate.removeClass($element, DIRTY_CLASS);
2016020474 $animate.addClass($element, PRISTINE_CLASS);
20475
+ };
20476
+
20477
+ /**
20478
+ * @ngdoc method
20479
+ * @name ngModel.NgModelController#$setDirty
20480
+ *
20481
+ * @description
20482
+ * Sets the control to its dirty state.
20483
+ *
20484
+ * This method can be called to remove the `ng-pristine` class and set the control to its dirty
20485
+ * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
20486
+ * from when first compiled.
20487
+ */
20488
+ this.$setDirty = function() {
20489
+ ctrl.$dirty = true;
20490
+ ctrl.$pristine = false;
20491
+ $animate.removeClass($element, PRISTINE_CLASS);
20492
+ $animate.addClass($element, DIRTY_CLASS);
20493
+ parentForm.$setDirty();
2016120494 };
2016220495
2016320496 /**
....@@ -20167,8 +20500,8 @@
2016720500 * @description
2016820501 * Sets the control to its untouched state.
2016920502 *
20170
- * This method can be called to remove the 'ng-touched' class and set the control to its
20171
- * untouched state (ng-untouched class). Upon compilation, a model is set as untouched
20503
+ * This method can be called to remove the `ng-touched` class and set the control to its
20504
+ * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
2017220505 * by default, however this function can be used to restore that state if the model has
2017320506 * already been touched by the user.
2017420507 */
....@@ -20185,10 +20518,9 @@
2018520518 * @description
2018620519 * Sets the control to its touched state.
2018720520 *
20188
- * This method can be called to remove the 'ng-untouched' class and set the control to its
20189
- * touched state (ng-touched class). A model is considered to be touched when the user has
20190
- * first interacted (focussed) on the model input element and then shifted focus away (blurred)
20191
- * from the input element.
20521
+ * This method can be called to remove the `ng-untouched` class and set the control to its
20522
+ * touched state (`ng-touched` class). A model is considered to be touched when the user has
20523
+ * first focused the control element and then shifted focus away from the control (blur event).
2019220524 */
2019320525 this.$setTouched = function() {
2019420526 ctrl.$touched = true;
....@@ -20222,13 +20554,13 @@
2022220554 * angular.module('cancel-update-example', [])
2022320555 *
2022420556 * .controller('CancelUpdateController', ['$scope', function($scope) {
20225
- * $scope.resetWithCancel = function (e) {
20557
+ * $scope.resetWithCancel = function(e) {
2022620558 * if (e.keyCode == 27) {
2022720559 * $scope.myForm.myInput1.$rollbackViewValue();
2022820560 * $scope.myValue = '';
2022920561 * }
2023020562 * };
20231
- * $scope.resetWithoutCancel = function (e) {
20563
+ * $scope.resetWithoutCancel = function(e) {
2023220564 * if (e.keyCode == 27) {
2023320565 * $scope.myValue = '';
2023420566 * }
....@@ -20266,14 +20598,51 @@
2026620598 * @name ngModel.NgModelController#$validate
2026720599 *
2026820600 * @description
20269
- * Runs each of the registered validators (first synchronous validators and then asynchronous validators).
20601
+ * Runs each of the registered validators (first synchronous validators and then
20602
+ * asynchronous validators).
20603
+ * If the validity changes to invalid, the model will be set to `undefined`,
20604
+ * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
20605
+ * If the validity changes to valid, it will set the model to the last available valid
20606
+ * modelValue, i.e. either the last parsed value or the last value set from the scope.
2027020607 */
2027120608 this.$validate = function() {
2027220609 // ignore $validate before model is initialized
2027320610 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
2027420611 return;
2027520612 }
20276
- this.$$parseAndValidate();
20613
+
20614
+ var viewValue = ctrl.$$lastCommittedViewValue;
20615
+ // Note: we use the $$rawModelValue as $modelValue might have been
20616
+ // set to undefined during a view -> model update that found validation
20617
+ // errors. We can't parse the view here, since that could change
20618
+ // the model although neither viewValue nor the model on the scope changed
20619
+ var modelValue = ctrl.$$rawModelValue;
20620
+
20621
+ // Check if the there's a parse error, so we don't unset it accidentially
20622
+ var parserName = ctrl.$$parserName || 'parse';
20623
+ var parserValid = ctrl.$error[parserName] ? false : undefined;
20624
+
20625
+ var prevValid = ctrl.$valid;
20626
+ var prevModelValue = ctrl.$modelValue;
20627
+
20628
+ var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
20629
+
20630
+ ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
20631
+ // If there was no change in validity, don't update the model
20632
+ // This prevents changing an invalid modelValue to undefined
20633
+ if (!allowInvalid && prevValid !== allValid) {
20634
+ // Note: Don't check ctrl.$valid here, as we could have
20635
+ // external validators (e.g. calculated on the server),
20636
+ // that just call $setValidity and need the model value
20637
+ // to calculate their validity.
20638
+ ctrl.$modelValue = allValid ? modelValue : undefined;
20639
+
20640
+ if (ctrl.$modelValue !== prevModelValue) {
20641
+ ctrl.$$writeModelToScope();
20642
+ }
20643
+ }
20644
+ });
20645
+
2027720646 };
2027820647
2027920648 this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
....@@ -20392,11 +20761,7 @@
2039220761
2039320762 // change to dirty
2039420763 if (ctrl.$pristine) {
20395
- ctrl.$dirty = true;
20396
- ctrl.$pristine = false;
20397
- $animate.removeClass($element, PRISTINE_CLASS);
20398
- $animate.addClass($element, DIRTY_CLASS);
20399
- parentForm.$setDirty();
20764
+ this.$setDirty();
2040020765 }
2040120766 this.$$parseAndValidate();
2040220767 };
....@@ -20407,7 +20772,7 @@
2040720772 var parserValid = isUndefined(modelValue) ? undefined : true;
2040820773
2040920774 if (parserValid) {
20410
- for(var i = 0; i < ctrl.$parsers.length; i++) {
20775
+ for (var i = 0; i < ctrl.$parsers.length; i++) {
2041120776 modelValue = ctrl.$parsers[i](modelValue);
2041220777 if (isUndefined(modelValue)) {
2041320778 parserValid = false;
....@@ -20417,15 +20782,20 @@
2041720782 }
2041820783 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
2041920784 // ctrl.$modelValue has not been touched yet...
20420
- ctrl.$modelValue = ngModelGet();
20785
+ ctrl.$modelValue = ngModelGet($scope);
2042120786 }
2042220787 var prevModelValue = ctrl.$modelValue;
2042320788 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
20789
+ ctrl.$$rawModelValue = modelValue;
20790
+
2042420791 if (allowInvalid) {
2042520792 ctrl.$modelValue = modelValue;
2042620793 writeToModelIfNeeded();
2042720794 }
20428
- ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
20795
+
20796
+ // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
20797
+ // This can happen if e.g. $setViewValue is called from inside a parser
20798
+ ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
2042920799 if (!allowInvalid) {
2043020800 // Note: Don't check ctrl.$valid here, as we could have
2043120801 // external validators (e.g. calculated on the server),
....@@ -20444,11 +20814,11 @@
2044420814 };
2044520815
2044620816 this.$$writeModelToScope = function() {
20447
- ngModelSet(ctrl.$modelValue);
20817
+ ngModelSet($scope, ctrl.$modelValue);
2044820818 forEach(ctrl.$viewChangeListeners, function(listener) {
2044920819 try {
2045020820 listener();
20451
- } catch(e) {
20821
+ } catch (e) {
2045220822 $exceptionHandler(e);
2045320823 }
2045420824 });
....@@ -20540,18 +20910,18 @@
2054020910 // ng-change executes in apply phase
2054120911 // 4. view should be changed back to 'a'
2054220912 $scope.$watch(function ngModelWatch() {
20543
- var modelValue = ngModelGet();
20913
+ var modelValue = ngModelGet($scope);
2054420914
2054520915 // if scope model value and ngModel value are out of sync
2054620916 // TODO(perf): why not move this to the action fn?
2054720917 if (modelValue !== ctrl.$modelValue) {
20548
- ctrl.$modelValue = modelValue;
20918
+ ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
2054920919
2055020920 var formatters = ctrl.$formatters,
2055120921 idx = formatters.length;
2055220922
2055320923 var viewValue = modelValue;
20554
- while(idx--) {
20924
+ while (idx--) {
2055520925 viewValue = formatters[idx](viewValue);
2055620926 }
2055720927 if (ctrl.$viewValue !== viewValue) {
....@@ -20572,6 +20942,7 @@
2057220942 * @name ngModel
2057320943 *
2057420944 * @element input
20945
+ * @priority 1
2057520946 *
2057620947 * @description
2057720948 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
....@@ -20593,7 +20964,7 @@
2059320964 *
2059420965 * For best practices on using `ngModel`, see:
2059520966 *
20596
- * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes]
20967
+ * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
2059720968 *
2059820969 * For basic examples, how to use `ngModel`, see:
2059920970 *
....@@ -20605,7 +20976,7 @@
2060520976 * - {@link input[email] email}
2060620977 * - {@link input[url] url}
2060720978 * - {@link input[date] date}
20608
- * - {@link input[dateTimeLocal] dateTimeLocal}
20979
+ * - {@link input[datetime-local] datetime-local}
2060920980 * - {@link input[time] time}
2061020981 * - {@link input[month] month}
2061120982 * - {@link input[week] week}
....@@ -20616,10 +20987,15 @@
2061620987 * The following CSS classes are added and removed on the associated input/select/textarea element
2061720988 * depending on the validity of the model.
2061820989 *
20619
- * - `ng-valid` is set if the model is valid.
20620
- * - `ng-invalid` is set if the model is invalid.
20621
- * - `ng-pristine` is set if the model is pristine.
20622
- * - `ng-dirty` is set if the model is dirty.
20990
+ * - `ng-valid`: the model is valid
20991
+ * - `ng-invalid`: the model is invalid
20992
+ * - `ng-valid-[key]`: for each valid key added by `$setValidity`
20993
+ * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
20994
+ * - `ng-pristine`: the control hasn't been interacted with yet
20995
+ * - `ng-dirty`: the control has been interacted with
20996
+ * - `ng-touched`: the control has been blurred
20997
+ * - `ng-untouched`: the control hasn't been blurred
20998
+ * - `ng-pending`: any `$asyncValidators` are unfulfilled
2062320999 *
2062421000 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
2062521001 *
....@@ -20713,7 +21089,7 @@
2071321089 .controller('ExampleController', ['$scope', function($scope) {
2071421090 var _name = 'Brian';
2071521091 $scope.user = {
20716
- name: function (newName) {
21092
+ name: function(newName) {
2071721093 if (angular.isDefined(newName)) {
2071821094 _name = newName;
2071921095 }
....@@ -20724,7 +21100,7 @@
2072421100 </file>
2072521101 * </example>
2072621102 */
20727
-var ngModelDirective = function() {
21103
+var ngModelDirective = ['$rootScope', function($rootScope) {
2072821104 return {
2072921105 restrict: 'A',
2073021106 require: ['ngModel', '^?form', '^?ngModelOptions'],
....@@ -20768,15 +21144,17 @@
2076821144 element.on('blur', function(ev) {
2076921145 if (modelCtrl.$touched) return;
2077021146
20771
- scope.$apply(function() {
20772
- modelCtrl.$setTouched();
20773
- });
21147
+ if ($rootScope.$$phase) {
21148
+ scope.$evalAsync(modelCtrl.$setTouched);
21149
+ } else {
21150
+ scope.$apply(modelCtrl.$setTouched);
21151
+ }
2077421152 });
2077521153 }
2077621154 };
2077721155 }
2077821156 };
20779
-};
21157
+}];
2078021158
2078121159
2078221160 /**
....@@ -20865,8 +21243,8 @@
2086521243 if (!ctrl) return;
2086621244 attr.required = true; // force truthy in case we are on non input element
2086721245
20868
- ctrl.$validators.required = function(value) {
20869
- return !attr.required || !ctrl.$isEmpty(value);
21246
+ ctrl.$validators.required = function(modelValue, viewValue) {
21247
+ return !attr.required || !ctrl.$isEmpty(viewValue);
2087021248 };
2087121249
2087221250 attr.$observe('required', function() {
....@@ -20887,7 +21265,7 @@
2088721265 var regexp, patternExp = attr.ngPattern || attr.pattern;
2088821266 attr.$observe('pattern', function(regex) {
2088921267 if (isString(regex) && regex.length > 0) {
20890
- regex = new RegExp(regex);
21268
+ regex = new RegExp('^' + regex + '$');
2089121269 }
2089221270
2089321271 if (regex && !regex.test) {
....@@ -20915,13 +21293,14 @@
2091521293 link: function(scope, elm, attr, ctrl) {
2091621294 if (!ctrl) return;
2091721295
20918
- var maxlength = 0;
21296
+ var maxlength = -1;
2091921297 attr.$observe('maxlength', function(value) {
20920
- maxlength = int(value) || 0;
21298
+ var intVal = int(value);
21299
+ maxlength = isNaN(intVal) ? -1 : intVal;
2092121300 ctrl.$validate();
2092221301 });
2092321302 ctrl.$validators.maxlength = function(modelValue, viewValue) {
20924
- return ctrl.$isEmpty(modelValue) || viewValue.length <= maxlength;
21303
+ return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
2092521304 };
2092621305 }
2092721306 };
....@@ -20940,7 +21319,7 @@
2094021319 ctrl.$validate();
2094121320 });
2094221321 ctrl.$validators.minlength = function(modelValue, viewValue) {
20943
- return ctrl.$isEmpty(modelValue) || viewValue.length >= minlength;
21322
+ return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
2094421323 };
2094521324 }
2094621325 };
....@@ -21080,12 +21459,17 @@
2108021459 * @name ngValue
2108121460 *
2108221461 * @description
21083
- * Binds the given expression to the value of `input[select]` or `input[radio]`, so
21084
- * that when the element is selected, the `ngModel` of that element is set to the
21085
- * bound value.
21462
+ * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
21463
+ * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
21464
+ * the bound value.
2108621465 *
21087
- * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as
21088
- * shown below.
21466
+ * `ngValue` is useful when dynamically generating lists of radio buttons using
21467
+ * {@link ngRepeat `ngRepeat`}, as shown below.
21468
+ *
21469
+ * Likewise, `ngValue` can be used to generate `<option>` elements for
21470
+ * the {@link select `select`} element. In that case however, only strings are supported
21471
+ * for the `value `attribute, so the resulting `ngModel` will always be a string.
21472
+ * Support for `select` models with non-string values is available via `ngOptions`.
2108921473 *
2109021474 * @element input
2109121475 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
....@@ -21173,7 +21557,7 @@
2117321557 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
2117421558 *
2117521559 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
21176
- * - `updateOn`: string specifying which event should be the input bound to. You can set several
21560
+ * - `updateOn`: string specifying which event should the input be bound to. You can set several
2117721561 * events using an space delimited list. There is a special event called `default` that
2117821562 * matches the default events belonging of the control.
2117921563 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
....@@ -21215,7 +21599,7 @@
2121521599 .controller('ExampleController', ['$scope', function($scope) {
2121621600 $scope.user = { name: 'say', data: '' };
2121721601
21218
- $scope.cancel = function (e) {
21602
+ $scope.cancel = function(e) {
2121921603 if (e.keyCode == 27) {
2122021604 $scope.userForm.userName.$rollbackViewValue();
2122121605 }
....@@ -21289,7 +21673,7 @@
2128921673 .controller('ExampleController', ['$scope', function($scope) {
2129021674 var _name = 'Brian';
2129121675 $scope.user = {
21292
- name: function (newName) {
21676
+ name: function(newName) {
2129321677 return angular.isDefined(newName) ? (_name = newName) : _name;
2129421678 }
2129521679 };
....@@ -21512,7 +21896,7 @@
2151221896 <file name="index.html">
2151321897 <script>
2151421898 angular.module('bindExample', [])
21515
- .controller('ExampleController', ['$scope', function ($scope) {
21899
+ .controller('ExampleController', ['$scope', function($scope) {
2151621900 $scope.salutation = 'Hello';
2151721901 $scope.name = 'World';
2151821902 }]);
....@@ -21563,12 +21947,11 @@
2156321947 * @name ngBindHtml
2156421948 *
2156521949 * @description
21566
- * Creates a binding that will innerHTML the result of evaluating the `expression` into the current
21567
- * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
21568
- * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
21569
- * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
21570
- * core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
21571
- * include "angular-sanitize.js" in your application.
21950
+ * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
21951
+ * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
21952
+ * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
21953
+ * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
21954
+ * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
2157221955 *
2157321956 * You may also bypass sanitization for values you know are safe. To do so, bind to
2157421957 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
....@@ -21667,10 +22050,10 @@
2166722050 attr.$removeClass(newClasses);
2166822051 }
2166922052
21670
- function digestClassCounts (classes, count) {
22053
+ function digestClassCounts(classes, count) {
2167122054 var classCounts = element.data('$classCounts') || {};
2167222055 var classesToUpdate = [];
21673
- forEach(classes, function (className) {
22056
+ forEach(classes, function(className) {
2167422057 if (count > 0 || classCounts[className]) {
2167522058 classCounts[className] = (classCounts[className] || 0) + count;
2167622059 if (classCounts[className] === +(count > 0)) {
....@@ -21682,7 +22065,7 @@
2168222065 return classesToUpdate.join(' ');
2168322066 }
2168422067
21685
- function updateClasses (oldClasses, newClasses) {
22068
+ function updateClasses(oldClasses, newClasses) {
2168622069 var toAdd = arrayDifference(newClasses, oldClasses);
2168722070 var toRemove = arrayDifference(oldClasses, newClasses);
2168822071 toAdd = digestClassCounts(toAdd, 1);
....@@ -21714,23 +22097,23 @@
2171422097 var values = [];
2171522098
2171622099 outer:
21717
- for(var i = 0; i < tokens1.length; i++) {
22100
+ for (var i = 0; i < tokens1.length; i++) {
2171822101 var token = tokens1[i];
21719
- for(var j = 0; j < tokens2.length; j++) {
21720
- if(token == tokens2[j]) continue outer;
22102
+ for (var j = 0; j < tokens2.length; j++) {
22103
+ if (token == tokens2[j]) continue outer;
2172122104 }
2172222105 values.push(token);
2172322106 }
2172422107 return values;
2172522108 }
2172622109
21727
- function arrayClasses (classVal) {
22110
+ function arrayClasses(classVal) {
2172822111 if (isArray(classVal)) {
2172922112 return classVal;
2173022113 } else if (isString(classVal)) {
2173122114 return classVal.split(' ');
2173222115 } else if (isObject(classVal)) {
21733
- var classes = [], i = 0;
22116
+ var classes = [];
2173422117 forEach(classVal, function(v, k) {
2173522118 if (v) {
2173622119 classes = classes.concat(k.split(' '));
....@@ -22489,10 +22872,8 @@
2248922872 </example>
2249022873 */
2249122874 /*
22492
- * A directive that allows creation of custom onclick handlers that are defined as angular
22493
- * expressions and are compiled and executed within the current scope.
22494
- *
22495
- * Events that are handled via these handler are always configured not to propagate further.
22875
+ * A collection of directives that allows creation of custom event handlers that are defined as
22876
+ * angular expressions and are compiled and executed within the current scope.
2249622877 */
2249722878 var ngEventDirectives = {};
2249822879
....@@ -22511,7 +22892,11 @@
2251122892 return {
2251222893 restrict: 'A',
2251322894 compile: function($element, attr) {
22514
- var fn = $parse(attr[directiveName]);
22895
+ // We expose the powerful $event object on the scope that provides access to the Window,
22896
+ // etc. that isn't protected by the fast paths in $parse. We explicitly request better
22897
+ // checks at the cost of speed since event handler expressions are not executed as
22898
+ // frequently as regular change detection.
22899
+ var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
2251522900 return function ngEventHandler(scope, element) {
2251622901 element.on(eventName, function(event) {
2251722902 var callback = function() {
....@@ -23022,13 +23407,13 @@
2302223407 terminal: true,
2302323408 restrict: 'A',
2302423409 $$tlb: true,
23025
- link: function ($scope, $element, $attr, ctrl, $transclude) {
23410
+ link: function($scope, $element, $attr, ctrl, $transclude) {
2302623411 var block, childScope, previousElements;
2302723412 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
2302823413
2302923414 if (value) {
2303023415 if (!childScope) {
23031
- $transclude(function (clone, newScope) {
23416
+ $transclude(function(clone, newScope) {
2303223417 childScope = newScope;
2303323418 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
2303423419 // Note: We only need the first/last node of the cloned nodes.
....@@ -23260,15 +23645,15 @@
2326023645 currentElement;
2326123646
2326223647 var cleanupLastIncludeContent = function() {
23263
- if(previousElement) {
23648
+ if (previousElement) {
2326423649 previousElement.remove();
2326523650 previousElement = null;
2326623651 }
23267
- if(currentScope) {
23652
+ if (currentScope) {
2326823653 currentScope.$destroy();
2326923654 currentScope = null;
2327023655 }
23271
- if(currentElement) {
23656
+ if (currentElement) {
2327223657 $animate.leave(currentElement).then(function() {
2327323658 previousElement = null;
2327423659 });
....@@ -23346,7 +23731,7 @@
2334623731 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
2334723732 function namespaceAdaptedClone(clone) {
2334823733 $element.append(clone);
23349
- }, undefined, undefined, $element);
23734
+ }, {futureParentElement: $element});
2335023735 return;
2335123736 }
2335223737
....@@ -23630,7 +24015,9 @@
2363024015 </example>
2363124016 */
2363224017 var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
23633
- var BRACE = /{}/g;
24018
+ var BRACE = /{}/g,
24019
+ IS_WHEN = /^when(Minus)?(.+)$/;
24020
+
2363424021 return {
2363524022 restrict: 'EA',
2363624023 link: function(scope, element, attr) {
....@@ -23641,34 +24028,44 @@
2364124028 whensExpFns = {},
2364224029 startSymbol = $interpolate.startSymbol(),
2364324030 endSymbol = $interpolate.endSymbol(),
23644
- isWhen = /^when(Minus)?(.+)$/;
24031
+ braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
24032
+ watchRemover = angular.noop,
24033
+ lastCount;
2364524034
2364624035 forEach(attr, function(expression, attributeName) {
23647
- if (isWhen.test(attributeName)) {
23648
- whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] =
23649
- element.attr(attr.$attr[attributeName]);
24036
+ var tmpMatch = IS_WHEN.exec(attributeName);
24037
+ if (tmpMatch) {
24038
+ var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
24039
+ whens[whenKey] = element.attr(attr.$attr[attributeName]);
2365024040 }
2365124041 });
2365224042 forEach(whens, function(expression, key) {
23653
- whensExpFns[key] =
23654
- $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' +
23655
- offset + endSymbol));
24043
+ whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
24044
+
2365624045 });
2365724046
23658
- scope.$watch(function ngPluralizeWatch() {
23659
- var value = parseFloat(scope.$eval(numberExp));
24047
+ scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
24048
+ var count = parseFloat(newVal);
24049
+ var countIsNaN = isNaN(count);
2366024050
23661
- if (!isNaN(value)) {
23662
- //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
23663
- //check it against pluralization rules in $locale service
23664
- if (!(value in whens)) value = $locale.pluralCat(value - offset);
23665
- return whensExpFns[value](scope);
23666
- } else {
23667
- return '';
24051
+ if (!countIsNaN && !(count in whens)) {
24052
+ // If an explicit number rule such as 1, 2, 3... is defined, just use it.
24053
+ // Otherwise, check it against pluralization rules in $locale service.
24054
+ count = $locale.pluralCat(count - offset);
2366824055 }
23669
- }, function ngPluralizeWatchAction(newVal) {
23670
- element.text(newVal);
24056
+
24057
+ // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
24058
+ // In JS `NaN !== NaN`, so we have to exlicitly check.
24059
+ if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) {
24060
+ watchRemover();
24061
+ watchRemover = scope.$watch(whensExpFns[count], updateElementText);
24062
+ lastCount = count;
24063
+ }
2367124064 });
24065
+
24066
+ function updateElementText(newText) {
24067
+ element.text(newText || '');
24068
+ }
2367224069 }
2367324070 };
2367424071 }];
....@@ -23951,10 +24348,10 @@
2395124348 if (trackByExp) {
2395224349 trackByExpGetter = $parse(trackByExp);
2395324350 } else {
23954
- trackByIdArrayFn = function (key, value) {
24351
+ trackByIdArrayFn = function(key, value) {
2395524352 return hashKey(value);
2395624353 };
23957
- trackByIdObjFn = function (key) {
24354
+ trackByIdObjFn = function(key) {
2395824355 return key;
2395924356 };
2396024357 }
....@@ -24034,12 +24431,12 @@
2403424431 nextBlockOrder[index] = block;
2403524432 } else if (nextBlockMap[trackById]) {
2403624433 // if collision detected. restore lastBlockMap and throw an error
24037
- forEach(nextBlockOrder, function (block) {
24434
+ forEach(nextBlockOrder, function(block) {
2403824435 if (block && block.scope) lastBlockMap[block.id] = block;
2403924436 });
2404024437 throw ngRepeatMinErr('dupes',
2404124438 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
24042
- expression, trackById, toJson(value));
24439
+ expression, trackById, value);
2404324440 } else {
2404424441 // new never before seen block
2404524442 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
....@@ -24150,17 +24547,17 @@
2415024547 *
2415124548 * ### Overriding `.ng-hide`
2415224549 *
24153
- * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change
24550
+ * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
2415424551 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
2415524552 * class in CSS:
2415624553 *
2415724554 * ```css
2415824555 * .ng-hide {
2415924556 * /&#42; this is just another form of hiding an element &#42;/
24160
- * display:block!important;
24161
- * position:absolute;
24162
- * top:-9999px;
24163
- * left:-9999px;
24557
+ * display: block!important;
24558
+ * position: absolute;
24559
+ * top: -9999px;
24560
+ * left: -9999px;
2416424561 * }
2416524562 * ```
2416624563 *
....@@ -24180,13 +24577,13 @@
2418024577 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
2418124578 * /&#42; this is required as of 1.3x to properly
2418224579 * apply all styling in a show/hide animation &#42;/
24183
- * transition:0s linear all;
24580
+ * transition: 0s linear all;
2418424581 * }
2418524582 *
2418624583 * .my-element.ng-hide-add-active,
2418724584 * .my-element.ng-hide-remove-active {
2418824585 * /&#42; the transition is defined in the active class &#42;/
24189
- * transition:1s linear all;
24586
+ * transition: 1s linear all;
2419024587 * }
2419124588 *
2419224589 * .my-element.ng-hide-add { ... }
....@@ -24224,33 +24621,33 @@
2422424621 </div>
2422524622 </file>
2422624623 <file name="glyphicons.css">
24227
- @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css);
24624
+ @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
2422824625 </file>
2422924626 <file name="animations.css">
2423024627 .animate-show {
24231
- line-height:20px;
24232
- opacity:1;
24233
- padding:10px;
24234
- border:1px solid black;
24235
- background:white;
24628
+ line-height: 20px;
24629
+ opacity: 1;
24630
+ padding: 10px;
24631
+ border: 1px solid black;
24632
+ background: white;
2423624633 }
2423724634
2423824635 .animate-show.ng-hide-add.ng-hide-add-active,
2423924636 .animate-show.ng-hide-remove.ng-hide-remove-active {
24240
- -webkit-transition:all linear 0.5s;
24241
- transition:all linear 0.5s;
24637
+ -webkit-transition: all linear 0.5s;
24638
+ transition: all linear 0.5s;
2424224639 }
2424324640
2424424641 .animate-show.ng-hide {
24245
- line-height:0;
24246
- opacity:0;
24247
- padding:0 10px;
24642
+ line-height: 0;
24643
+ opacity: 0;
24644
+ padding: 0 10px;
2424824645 }
2424924646
2425024647 .check-element {
24251
- padding:10px;
24252
- border:1px solid black;
24253
- background:white;
24648
+ padding: 10px;
24649
+ border: 1px solid black;
24650
+ background: white;
2425424651 }
2425524652 </file>
2425624653 <file name="protractor.js" type="protractor">
....@@ -24274,13 +24671,13 @@
2427424671 restrict: 'A',
2427524672 multiElement: true,
2427624673 link: function(scope, element, attr) {
24277
- scope.$watch(attr.ngShow, function ngShowWatchAction(value){
24674
+ scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
2427824675 // we're adding a temporary, animation-specific class for ng-hide since this way
2427924676 // we can control when the element is actually displayed on screen without having
2428024677 // to have a global/greedy CSS selector that breaks when other animations are run.
2428124678 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
2428224679 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
24283
- tempClasses : NG_HIDE_IN_PROGRESS_CLASS
24680
+ tempClasses: NG_HIDE_IN_PROGRESS_CLASS
2428424681 });
2428524682 });
2428624683 }
....@@ -24324,17 +24721,17 @@
2432424721 *
2432524722 * ### Overriding `.ng-hide`
2432624723 *
24327
- * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change
24724
+ * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
2432824725 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
2432924726 * class in CSS:
2433024727 *
2433124728 * ```css
2433224729 * .ng-hide {
2433324730 * /&#42; this is just another form of hiding an element &#42;/
24334
- * display:block!important;
24335
- * position:absolute;
24336
- * top:-9999px;
24337
- * left:-9999px;
24731
+ * display: block!important;
24732
+ * position: absolute;
24733
+ * top: -9999px;
24734
+ * left: -9999px;
2433824735 * }
2433924736 * ```
2434024737 *
....@@ -24351,7 +24748,7 @@
2435124748 * //a working example can be found at the bottom of this page
2435224749 * //
2435324750 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
24354
- * transition:0.5s linear all;
24751
+ * transition: 0.5s linear all;
2435524752 * }
2435624753 *
2435724754 * .my-element.ng-hide-add { ... }
....@@ -24389,29 +24786,29 @@
2438924786 </div>
2439024787 </file>
2439124788 <file name="glyphicons.css">
24392
- @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css);
24789
+ @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
2439324790 </file>
2439424791 <file name="animations.css">
2439524792 .animate-hide {
24396
- -webkit-transition:all linear 0.5s;
24397
- transition:all linear 0.5s;
24398
- line-height:20px;
24399
- opacity:1;
24400
- padding:10px;
24401
- border:1px solid black;
24402
- background:white;
24793
+ -webkit-transition: all linear 0.5s;
24794
+ transition: all linear 0.5s;
24795
+ line-height: 20px;
24796
+ opacity: 1;
24797
+ padding: 10px;
24798
+ border: 1px solid black;
24799
+ background: white;
2440324800 }
2440424801
2440524802 .animate-hide.ng-hide {
24406
- line-height:0;
24407
- opacity:0;
24408
- padding:0 10px;
24803
+ line-height: 0;
24804
+ opacity: 0;
24805
+ padding: 0 10px;
2440924806 }
2441024807
2441124808 .check-element {
24412
- padding:10px;
24413
- border:1px solid black;
24414
- background:white;
24809
+ padding: 10px;
24810
+ border: 1px solid black;
24811
+ background: white;
2441524812 }
2441624813 </file>
2441724814 <file name="protractor.js" type="protractor">
....@@ -24435,11 +24832,11 @@
2443524832 restrict: 'A',
2443624833 multiElement: true,
2443724834 link: function(scope, element, attr) {
24438
- scope.$watch(attr.ngHide, function ngHideWatchAction(value){
24835
+ scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
2443924836 // The comment inside of the ngShowDirective explains why we add and
2444024837 // remove a temporary class for the show/hide animation
2444124838 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
24442
- tempClasses : NG_HIDE_IN_PROGRESS_CLASS
24839
+ tempClasses: NG_HIDE_IN_PROGRESS_CLASS
2444324840 });
2444424841 });
2444524842 }
....@@ -24740,7 +25137,7 @@
2474025137 }]);
2474125138 </script>
2474225139 <div ng-controller="ExampleController">
24743
- <input ng-model="title"><br>
25140
+ <input ng-model="title"> <br/>
2474425141 <textarea ng-model="text"></textarea> <br/>
2474525142 <pane title="{{title}}">{{text}}</pane>
2474625143 </div>
....@@ -24818,7 +25215,6 @@
2481825215 compile: function(element, attr) {
2481925216 if (attr.type == 'text/ng-template') {
2482025217 var templateUrl = attr.id,
24821
- // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
2482225218 text = element[0].text;
2482325219
2482425220 $templateCache.put(templateUrl, text);
....@@ -24842,34 +25238,37 @@
2484225238 * elements for the `<select>` element using the array or object obtained by evaluating the
2484325239 * `ngOptions` comprehension_expression.
2484425240 *
25241
+ * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
25242
+ * similar result. However, the `ngOptions` provides some benefits such as reducing memory and
25243
+ * increasing speed by not creating a new scope for each repeated instance, as well as providing
25244
+ * more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions` should be
25245
+ * used when the `select` model needs to be bound to a non-string value. This is because an option
25246
+ * element can only be bound to string values at present.
25247
+ *
2484525248 * When an item in the `<select>` menu is selected, the array element or object property
2484625249 * represented by the selected option will be bound to the model identified by the `ngModel`
2484725250 * directive.
24848
- *
24849
- * <div class="alert alert-warning">
24850
- * **Note:** `ngModel` compares by reference, not value. This is important when binding to an
24851
- * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
24852
- * </div>
2485325251 *
2485425252 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
2485525253 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
2485625254 * option. See example below for demonstration.
2485725255 *
2485825256 * <div class="alert alert-warning">
24859
- * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead
24860
- * of {@link ng.directive:ngRepeat ngRepeat} when you want the
24861
- * `select` model to be bound to a non-string value. This is because an option element can only
24862
- * be bound to string values at present.
25257
+ * **Note:** `ngModel` compares by reference, not value. This is important when binding to an
25258
+ * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
2486325259 * </div>
2486425260 *
24865
- * <div class="alert alert-info">
24866
- * **Note:** Using `select as` will bind the result of the `select as` expression to the model, but
25261
+ * ## `select as`
25262
+ *
25263
+ * Using `select as` will bind the result of the `select as` expression to the model, but
2486725264 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
24868
- * or property name (for object data sources) of the value within the collection.
24869
- * </div>
25265
+ * or property name (for object data sources) of the value within the collection. If a `track by` expression
25266
+ * is used, the result of that expression will be set as the value of the `option` and `select` elements.
2487025267 *
24871
- * **Note:** Using `select as` together with `trackexpr` is not recommended.
24872
- * Reasoning:
25268
+ * ### `select as` with `track by`
25269
+ *
25270
+ * Using `select as` together with `track by` is not recommended. Reasoning:
25271
+ *
2487325272 * - Example: &lt;select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"&gt;
2487425273 * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
2487525274 * $scope.selected = {name: 'aSubItem'};
....@@ -24893,8 +25292,10 @@
2489325292 * * for array data sources:
2489425293 * * `label` **`for`** `value` **`in`** `array`
2489525294 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
24896
- * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
24897
- * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
25295
+ * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
25296
+ * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
25297
+ * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
25298
+ * (for including a filter with `track by`)
2489825299 * * for object data sources:
2489925300 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
2490025301 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
....@@ -25036,7 +25437,7 @@
2503625437 self.removeOption = function(value) {
2503725438 if (this.hasOption(value)) {
2503825439 delete optionsMap[value];
25039
- if (ngModelCtrl.$viewValue == value) {
25440
+ if (ngModelCtrl.$viewValue === value) {
2504025441 this.renderUnknownOption(value);
2504125442 }
2504225443 }
....@@ -25080,7 +25481,7 @@
2508025481 unknownOption = optionTemplate.clone();
2508125482
2508225483 // find "null" option
25083
- for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
25484
+ for (var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
2508425485 if (children[i].value === '') {
2508525486 emptyOption = nullOption = children.eq(i);
2508625487 break;
....@@ -25182,6 +25583,7 @@
2518225583 valuesFn = $parse(match[7]),
2518325584 track = match[8],
2518425585 trackFn = track ? $parse(match[8]) : null,
25586
+ trackKeysCache = {},
2518525587 // This is an array of array of existing option groups in DOM.
2518625588 // We try to reuse these if possible
2518725589 // - optionGroupsCache[0] is the options with no option group
....@@ -25227,17 +25629,16 @@
2522725629
2522825630 function selectionChanged() {
2522925631 scope.$apply(function() {
25230
- var optionGroup,
25231
- collection = valuesFn(scope) || [],
25232
- key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
25632
+ var collection = valuesFn(scope) || [];
2523325633 var viewValue;
2523425634 if (multiple) {
2523525635 viewValue = [];
2523625636 forEach(selectElement.val(), function(selectedKey) {
25637
+ selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey;
2523725638 viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
2523825639 });
2523925640 } else {
25240
- var selectedKey = selectElement.val();
25641
+ var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val();
2524125642 viewValue = getViewValue(selectedKey, collection[selectedKey]);
2524225643 }
2524325644 ctrl.$setViewValue(viewValue);
....@@ -25307,7 +25708,7 @@
2530725708 if (multiple) {
2530825709 return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
2530925710 } else {
25310
- return viewValue == callExpression(compareValueFn, key, value);
25711
+ return viewValue === callExpression(compareValueFn, key, value);
2531125712 }
2531225713 };
2531325714 }
....@@ -25359,14 +25760,17 @@
2535925760 anySelected = false,
2536025761 lastElement,
2536125762 element,
25362
- label;
25763
+ label,
25764
+ optionId;
25765
+
25766
+ trackKeysCache = {};
2536325767
2536425768 // We now build up the list of options we need (we merge later)
2536525769 for (index = 0; length = keys.length, index < length; index++) {
2536625770 key = index;
2536725771 if (keyName) {
2536825772 key = keys[index];
25369
- if ( key.charAt(0) === '$' ) continue;
25773
+ if (key.charAt(0) === '$') continue;
2537025774 }
2537125775 value = values[key];
2537225776
....@@ -25383,9 +25787,14 @@
2538325787
2538425788 // doing displayFn(scope, locals) || '' overwrites zero values
2538525789 label = isDefined(label) ? label : '';
25790
+ optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index);
25791
+ if (trackFn) {
25792
+ trackKeysCache[optionId] = key;
25793
+ }
25794
+
2538625795 optionGroup.push({
2538725796 // either the index into array or key from object
25388
- id: (keyName ? keys[index] : index),
25797
+ id: optionId,
2538925798 label: label,
2539025799 selected: selected // determine if we should be selected
2539125800 });
....@@ -25430,15 +25839,16 @@
2543025839 }
2543125840
2543225841 lastElement = null; // start at the beginning
25433
- for(index = 0, length = optionGroup.length; index < length; index++) {
25842
+ for (index = 0, length = optionGroup.length; index < length; index++) {
2543425843 option = optionGroup[index];
25435
- if ((existingOption = existingOptions[index+1])) {
25844
+ if ((existingOption = existingOptions[index + 1])) {
2543625845 // reuse elements
2543725846 lastElement = existingOption.element;
2543825847 if (existingOption.label !== option.label) {
2543925848 updateLabelMap(labelMap, existingOption.label, false);
2544025849 updateLabelMap(labelMap, option.label, true);
2544125850 lastElement.text(existingOption.label = option.label);
25851
+ lastElement.prop('label', existingOption.label);
2544225852 }
2544325853 if (existingOption.id !== option.id) {
2544425854 lastElement.val(existingOption.id = option.id);
....@@ -25468,6 +25878,7 @@
2546825878 .val(option.id)
2546925879 .prop('selected', option.selected)
2547025880 .attr('selected', option.selected)
25881
+ .prop('label', option.label)
2547125882 .text(option.label);
2547225883 }
2547325884
....@@ -25488,23 +25899,28 @@
2548825899 }
2548925900 // remove any excessive OPTIONs in a group
2549025901 index++; // increment since the existingOptions[0] is parent element not OPTION
25491
- while(existingOptions.length > index) {
25902
+ while (existingOptions.length > index) {
2549225903 option = existingOptions.pop();
2549325904 updateLabelMap(labelMap, option.label, false);
2549425905 option.element.remove();
2549525906 }
25496
- forEach(labelMap, function (count, label) {
25497
- if (count > 0) {
25498
- selectCtrl.addOption(label);
25499
- } else if (count < 0) {
25500
- selectCtrl.removeOption(label);
25501
- }
25502
- });
2550325907 }
2550425908 // remove any excessive OPTGROUPs from select
25505
- while(optionGroupsCache.length > groupIndex) {
25506
- optionGroupsCache.pop()[0].element.remove();
25909
+ while (optionGroupsCache.length > groupIndex) {
25910
+ // remove all the labels in the option group
25911
+ optionGroup = optionGroupsCache.pop();
25912
+ for (index = 1; index < optionGroup.length; ++index) {
25913
+ updateLabelMap(labelMap, optionGroup[index].label, false);
25914
+ }
25915
+ optionGroup[0].element.remove();
2550725916 }
25917
+ forEach(labelMap, function(count, label) {
25918
+ if (count > 0) {
25919
+ selectCtrl.addOption(label);
25920
+ } else if (count < 0) {
25921
+ selectCtrl.removeOption(label);
25922
+ }
25923
+ });
2550825924 }
2550925925 }
2551025926 }
....@@ -25528,7 +25944,7 @@
2552825944 }
2552925945 }
2553025946
25531
- return function (scope, element, attr) {
25947
+ return function(scope, element, attr) {
2553225948 var selectCtrlName = '$selectController',
2553325949 parent = element.parent(),
2553425950 selectCtrl = parent.data(selectCtrlName) ||