UNPKG

1.38 MBJavaScriptView Raw
1/**
2 * @license AngularJS v1.8.3
3 * (c) 2010-2020 Google LLC. http://angularjs.org
4 * License: MIT
5 */
6(function(window) {'use strict';
7
8/* exported
9 minErrConfig,
10 errorHandlingConfig,
11 isValidObjectMaxDepth
12*/
13
14var minErrConfig = {
15 objectMaxDepth: 5,
16 urlErrorParamsEnabled: true
17};
18
19/**
20 * @ngdoc function
21 * @name angular.errorHandlingConfig
22 * @module ng
23 * @kind function
24 *
25 * @description
26 * Configure several aspects of error handling in AngularJS if used as a setter or return the
27 * current configuration if used as a getter. The following options are supported:
28 *
29 * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages.
30 *
31 * Omitted or undefined options will leave the corresponding configuration values unchanged.
32 *
33 * @param {Object=} config - The configuration object. May only contain the options that need to be
34 * updated. Supported keys:
35 *
36 * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a
37 * non-positive or non-numeric value, removes the max depth limit.
38 * Default: 5
39 *
40 * * `urlErrorParamsEnabled` **{Boolean}** - Specifies whether the generated error url will
41 * contain the parameters of the thrown error. Disabling the parameters can be useful if the
42 * generated error url is very long.
43 *
44 * Default: true. When used without argument, it returns the current value.
45 */
46function errorHandlingConfig(config) {
47 if (isObject(config)) {
48 if (isDefined(config.objectMaxDepth)) {
49 minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
50 }
51 if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) {
52 minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled;
53 }
54 } else {
55 return minErrConfig;
56 }
57}
58
59/**
60 * @private
61 * @param {Number} maxDepth
62 * @return {boolean}
63 */
64function isValidObjectMaxDepth(maxDepth) {
65 return isNumber(maxDepth) && maxDepth > 0;
66}
67
68
69/**
70 * @description
71 *
72 * This object provides a utility for producing rich Error messages within
73 * AngularJS. It can be called as follows:
74 *
75 * var exampleMinErr = minErr('example');
76 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
77 *
78 * The above creates an instance of minErr in the example namespace. The
79 * resulting error will have a namespaced error code of example.one. The
80 * resulting error will replace {0} with the value of foo, and {1} with the
81 * value of bar. The object is not restricted in the number of arguments it can
82 * take.
83 *
84 * If fewer arguments are specified than necessary for interpolation, the extra
85 * interpolation markers will be preserved in the final string.
86 *
87 * Since data will be parsed statically during a build step, some restrictions
88 * are applied with respect to how minErr instances are created and called.
89 * Instances should have names of the form namespaceMinErr for a minErr created
90 * using minErr('namespace'). Error codes, namespaces and template strings
91 * should all be static strings, not variables or general expressions.
92 *
93 * @param {string} module The namespace to use for the new minErr instance.
94 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
95 * error from returned function, for cases when a particular type of error is useful.
96 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
97 */
98
99function minErr(module, ErrorConstructor) {
100 ErrorConstructor = ErrorConstructor || Error;
101
102 var url = 'https://errors.angularjs.org/1.8.3/';
103 var regex = url.replace('.', '\\.') + '[\\s\\S]*';
104 var errRegExp = new RegExp(regex, 'g');
105
106 return function() {
107 var code = arguments[0],
108 template = arguments[1],
109 message = '[' + (module ? module + ':' : '') + code + '] ',
110 templateArgs = sliceArgs(arguments, 2).map(function(arg) {
111 return toDebugString(arg, minErrConfig.objectMaxDepth);
112 }),
113 paramPrefix, i;
114
115 // A minErr message has two parts: the message itself and the url that contains the
116 // encoded message.
117 // The message's parameters can contain other error messages which also include error urls.
118 // To prevent the messages from getting too long, we strip the error urls from the parameters.
119
120 message += template.replace(/\{\d+\}/g, function(match) {
121 var index = +match.slice(1, -1);
122
123 if (index < templateArgs.length) {
124 return templateArgs[index].replace(errRegExp, '');
125 }
126
127 return match;
128 });
129
130 message += '\n' + url + (module ? module + '/' : '') + code;
131
132 if (minErrConfig.urlErrorParamsEnabled) {
133 for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
134 message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
135 }
136 }
137
138 return new ErrorConstructor(message);
139 };
140}
141
142/* We need to tell ESLint what variables are being exported */
143/* exported
144 angular,
145 msie,
146 jqLite,
147 jQuery,
148 slice,
149 splice,
150 push,
151 toString,
152 minErrConfig,
153 errorHandlingConfig,
154 isValidObjectMaxDepth,
155 ngMinErr,
156 angularModule,
157 uid,
158 REGEX_STRING_REGEXP,
159 VALIDITY_STATE_PROPERTY,
160
161 lowercase,
162 uppercase,
163 nodeName_,
164 isArrayLike,
165 forEach,
166 forEachSorted,
167 reverseParams,
168 nextUid,
169 setHashKey,
170 extend,
171 toInt,
172 inherit,
173 merge,
174 noop,
175 identity,
176 valueFn,
177 isUndefined,
178 isDefined,
179 isObject,
180 isBlankObject,
181 isString,
182 isNumber,
183 isNumberNaN,
184 isDate,
185 isError,
186 isArray,
187 isFunction,
188 isRegExp,
189 isWindow,
190 isScope,
191 isFile,
192 isFormData,
193 isBlob,
194 isBoolean,
195 isPromiseLike,
196 trim,
197 escapeForRegexp,
198 isElement,
199 makeMap,
200 includes,
201 arrayRemove,
202 copy,
203 simpleCompare,
204 equals,
205 csp,
206 jq,
207 concat,
208 sliceArgs,
209 bind,
210 toJsonReplacer,
211 toJson,
212 fromJson,
213 convertTimezoneToLocal,
214 timezoneToOffset,
215 addDateMinutes,
216 startingTag,
217 tryDecodeURIComponent,
218 parseKeyValue,
219 toKeyValue,
220 encodeUriSegment,
221 encodeUriQuery,
222 angularInit,
223 bootstrap,
224 getTestability,
225 snake_case,
226 bindJQuery,
227 assertArg,
228 assertArgFn,
229 assertNotHasOwnProperty,
230 getter,
231 getBlockNodes,
232 hasOwnProperty,
233 createMap,
234 stringify,
235 UNSAFE_restoreLegacyJqLiteXHTMLReplacement,
236
237 NODE_TYPE_ELEMENT,
238 NODE_TYPE_ATTRIBUTE,
239 NODE_TYPE_TEXT,
240 NODE_TYPE_COMMENT,
241 NODE_TYPE_DOCUMENT,
242 NODE_TYPE_DOCUMENT_FRAGMENT
243*/
244
245////////////////////////////////////
246
247/**
248 * @ngdoc module
249 * @name ng
250 * @module ng
251 * @installation
252 * @description
253 *
254 * The ng module is loaded by default when an AngularJS application is started. The module itself
255 * contains the essential components for an AngularJS application to function. The table below
256 * lists a high level breakdown of each of the services/factories, filters, directives and testing
257 * components available within this core module.
258 *
259 */
260
261var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
262
263// The name of a form control's ValidityState property.
264// This is used so that it's possible for internal tests to create mock ValidityStates.
265var VALIDITY_STATE_PROPERTY = 'validity';
266
267
268var hasOwnProperty = Object.prototype.hasOwnProperty;
269
270/**
271 * @private
272 *
273 * @description Converts the specified string to lowercase.
274 * @param {string} string String to be converted to lowercase.
275 * @returns {string} Lowercased string.
276 */
277var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
278
279/**
280 * @private
281 *
282 * @description Converts the specified string to uppercase.
283 * @param {string} string String to be converted to uppercase.
284 * @returns {string} Uppercased string.
285 */
286var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
287
288
289var
290 msie, // holds major version number for IE, or NaN if UA is not IE.
291 jqLite, // delay binding since jQuery could be loaded after us.
292 jQuery, // delay binding
293 slice = [].slice,
294 splice = [].splice,
295 push = [].push,
296 toString = Object.prototype.toString,
297 getPrototypeOf = Object.getPrototypeOf,
298 ngMinErr = minErr('ng'),
299
300 /** @name angular */
301 angular = window.angular || (window.angular = {}),
302 angularModule,
303 uid = 0;
304
305// Support: IE 9-11 only
306/**
307 * documentMode is an IE-only property
308 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
309 */
310msie = window.document.documentMode;
311
312
313/**
314 * @private
315 * @param {*} obj
316 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
317 * String ...)
318 */
319function isArrayLike(obj) {
320
321 // `null`, `undefined` and `window` are not array-like
322 if (obj == null || isWindow(obj)) return false;
323
324 // arrays, strings and jQuery/jqLite objects are array like
325 // * jqLite is either the jQuery or jqLite constructor function
326 // * we have to check the existence of jqLite first as this method is called
327 // via the forEach method when constructing the jqLite object in the first place
328 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
329
330 // Support: iOS 8.2 (not reproducible in simulator)
331 // "length" in obj used to prevent JIT error (gh-11508)
332 var length = 'length' in Object(obj) && obj.length;
333
334 // NodeList objects (with `item` method) and
335 // other objects with suitable length characteristics are array-like
336 return isNumber(length) && (length >= 0 && (length - 1) in obj || typeof obj.item === 'function');
337
338}
339
340/**
341 * @ngdoc function
342 * @name angular.forEach
343 * @module ng
344 * @kind function
345 *
346 * @description
347 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
348 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
349 * is the value of an object property or an array element, `key` is the object property key or
350 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
351 *
352 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
353 * using the `hasOwnProperty` method.
354 *
355 * Unlike ES262's
356 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
357 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
358 * return the value provided.
359 *
360 ```js
361 var values = {name: 'misko', gender: 'male'};
362 var log = [];
363 angular.forEach(values, function(value, key) {
364 this.push(key + ': ' + value);
365 }, log);
366 expect(log).toEqual(['name: misko', 'gender: male']);
367 ```
368 *
369 * @param {Object|Array} obj Object to iterate over.
370 * @param {Function} iterator Iterator function.
371 * @param {Object=} context Object to become context (`this`) for the iterator function.
372 * @returns {Object|Array} Reference to `obj`.
373 */
374
375function forEach(obj, iterator, context) {
376 var key, length;
377 if (obj) {
378 if (isFunction(obj)) {
379 for (key in obj) {
380 if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) {
381 iterator.call(context, obj[key], key, obj);
382 }
383 }
384 } else if (isArray(obj) || isArrayLike(obj)) {
385 var isPrimitive = typeof obj !== 'object';
386 for (key = 0, length = obj.length; key < length; key++) {
387 if (isPrimitive || key in obj) {
388 iterator.call(context, obj[key], key, obj);
389 }
390 }
391 } else if (obj.forEach && obj.forEach !== forEach) {
392 obj.forEach(iterator, context, obj);
393 } else if (isBlankObject(obj)) {
394 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
395 for (key in obj) {
396 iterator.call(context, obj[key], key, obj);
397 }
398 } else if (typeof obj.hasOwnProperty === 'function') {
399 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
400 for (key in obj) {
401 if (obj.hasOwnProperty(key)) {
402 iterator.call(context, obj[key], key, obj);
403 }
404 }
405 } else {
406 // Slow path for objects which do not have a method `hasOwnProperty`
407 for (key in obj) {
408 if (hasOwnProperty.call(obj, key)) {
409 iterator.call(context, obj[key], key, obj);
410 }
411 }
412 }
413 }
414 return obj;
415}
416
417function forEachSorted(obj, iterator, context) {
418 var keys = Object.keys(obj).sort();
419 for (var i = 0; i < keys.length; i++) {
420 iterator.call(context, obj[keys[i]], keys[i]);
421 }
422 return keys;
423}
424
425
426/**
427 * when using forEach the params are value, key, but it is often useful to have key, value.
428 * @param {function(string, *)} iteratorFn
429 * @returns {function(*, string)}
430 */
431function reverseParams(iteratorFn) {
432 return function(value, key) {iteratorFn(key, value);};
433}
434
435/**
436 * A consistent way of creating unique IDs in angular.
437 *
438 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
439 * we hit number precision issues in JavaScript.
440 *
441 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
442 *
443 * @returns {number} an unique alpha-numeric string
444 */
445function nextUid() {
446 return ++uid;
447}
448
449
450/**
451 * Set or clear the hashkey for an object.
452 * @param obj object
453 * @param h the hashkey (!truthy to delete the hashkey)
454 */
455function setHashKey(obj, h) {
456 if (h) {
457 obj.$$hashKey = h;
458 } else {
459 delete obj.$$hashKey;
460 }
461}
462
463
464function baseExtend(dst, objs, deep) {
465 var h = dst.$$hashKey;
466
467 for (var i = 0, ii = objs.length; i < ii; ++i) {
468 var obj = objs[i];
469 if (!isObject(obj) && !isFunction(obj)) continue;
470 var keys = Object.keys(obj);
471 for (var j = 0, jj = keys.length; j < jj; j++) {
472 var key = keys[j];
473 var src = obj[key];
474
475 if (deep && isObject(src)) {
476 if (isDate(src)) {
477 dst[key] = new Date(src.valueOf());
478 } else if (isRegExp(src)) {
479 dst[key] = new RegExp(src);
480 } else if (src.nodeName) {
481 dst[key] = src.cloneNode(true);
482 } else if (isElement(src)) {
483 dst[key] = src.clone();
484 } else {
485 if (key !== '__proto__') {
486 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
487 baseExtend(dst[key], [src], true);
488 }
489 }
490 } else {
491 dst[key] = src;
492 }
493 }
494 }
495
496 setHashKey(dst, h);
497 return dst;
498}
499
500/**
501 * @ngdoc function
502 * @name angular.extend
503 * @module ng
504 * @kind function
505 *
506 * @description
507 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
508 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
509 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
510 *
511 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
512 * {@link angular.merge} for this.
513 *
514 * @param {Object} dst Destination object.
515 * @param {...Object} src Source object(s).
516 * @returns {Object} Reference to `dst`.
517 */
518function extend(dst) {
519 return baseExtend(dst, slice.call(arguments, 1), false);
520}
521
522
523/**
524* @ngdoc function
525* @name angular.merge
526* @module ng
527* @kind function
528*
529* @description
530* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
531* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
532* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
533*
534* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
535* objects, performing a deep copy.
536*
537* @deprecated
538* sinceVersion="1.6.5"
539* This function is deprecated, but will not be removed in the 1.x lifecycle.
540* There are edge cases (see {@link angular.merge#known-issues known issues}) that are not
541* supported by this function. We suggest using another, similar library for all-purpose merging,
542* such as [lodash's merge()](https://lodash.com/docs/4.17.4#merge).
543*
544* @knownIssue
545* This is a list of (known) object types that are not handled correctly by this function:
546* - [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob)
547* - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream)
548* - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient)
549* - AngularJS {@link $rootScope.Scope scopes};
550*
551* `angular.merge` also does not support merging objects with circular references.
552*
553* @param {Object} dst Destination object.
554* @param {...Object} src Source object(s).
555* @returns {Object} Reference to `dst`.
556*/
557function merge(dst) {
558 return baseExtend(dst, slice.call(arguments, 1), true);
559}
560
561
562
563function toInt(str) {
564 return parseInt(str, 10);
565}
566
567var isNumberNaN = Number.isNaN || function isNumberNaN(num) {
568 // eslint-disable-next-line no-self-compare
569 return num !== num;
570};
571
572
573function inherit(parent, extra) {
574 return extend(Object.create(parent), extra);
575}
576
577/**
578 * @ngdoc function
579 * @name angular.noop
580 * @module ng
581 * @kind function
582 *
583 * @description
584 * A function that performs no operations. This function can be useful when writing code in the
585 * functional style.
586 ```js
587 function foo(callback) {
588 var result = calculateResult();
589 (callback || angular.noop)(result);
590 }
591 ```
592 */
593function noop() {}
594noop.$inject = [];
595
596
597/**
598 * @ngdoc function
599 * @name angular.identity
600 * @module ng
601 * @kind function
602 *
603 * @description
604 * A function that returns its first argument. This function is useful when writing code in the
605 * functional style.
606 *
607 ```js
608 function transformer(transformationFn, value) {
609 return (transformationFn || angular.identity)(value);
610 };
611
612 // E.g.
613 function getResult(fn, input) {
614 return (fn || angular.identity)(input);
615 };
616
617 getResult(function(n) { return n * 2; }, 21); // returns 42
618 getResult(null, 21); // returns 21
619 getResult(undefined, 21); // returns 21
620 ```
621 *
622 * @param {*} value to be returned.
623 * @returns {*} the value passed in.
624 */
625function identity($) {return $;}
626identity.$inject = [];
627
628
629function valueFn(value) {return function valueRef() {return value;};}
630
631function hasCustomToString(obj) {
632 return isFunction(obj.toString) && obj.toString !== toString;
633}
634
635
636/**
637 * @ngdoc function
638 * @name angular.isUndefined
639 * @module ng
640 * @kind function
641 *
642 * @description
643 * Determines if a reference is undefined.
644 *
645 * @param {*} value Reference to check.
646 * @returns {boolean} True if `value` is undefined.
647 */
648function isUndefined(value) {return typeof value === 'undefined';}
649
650
651/**
652 * @ngdoc function
653 * @name angular.isDefined
654 * @module ng
655 * @kind function
656 *
657 * @description
658 * Determines if a reference is defined.
659 *
660 * @param {*} value Reference to check.
661 * @returns {boolean} True if `value` is defined.
662 */
663function isDefined(value) {return typeof value !== 'undefined';}
664
665
666/**
667 * @ngdoc function
668 * @name angular.isObject
669 * @module ng
670 * @kind function
671 *
672 * @description
673 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
674 * considered to be objects. Note that JavaScript arrays are objects.
675 *
676 * @param {*} value Reference to check.
677 * @returns {boolean} True if `value` is an `Object` but not `null`.
678 */
679function isObject(value) {
680 // http://jsperf.com/isobject4
681 return value !== null && typeof value === 'object';
682}
683
684
685/**
686 * Determine if a value is an object with a null prototype
687 *
688 * @returns {boolean} True if `value` is an `Object` with a null prototype
689 */
690function isBlankObject(value) {
691 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
692}
693
694
695/**
696 * @ngdoc function
697 * @name angular.isString
698 * @module ng
699 * @kind function
700 *
701 * @description
702 * Determines if a reference is a `String`.
703 *
704 * @param {*} value Reference to check.
705 * @returns {boolean} True if `value` is a `String`.
706 */
707function isString(value) {return typeof value === 'string';}
708
709
710/**
711 * @ngdoc function
712 * @name angular.isNumber
713 * @module ng
714 * @kind function
715 *
716 * @description
717 * Determines if a reference is a `Number`.
718 *
719 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
720 *
721 * If you wish to exclude these then you can use the native
722 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
723 * method.
724 *
725 * @param {*} value Reference to check.
726 * @returns {boolean} True if `value` is a `Number`.
727 */
728function isNumber(value) {return typeof value === 'number';}
729
730
731/**
732 * @ngdoc function
733 * @name angular.isDate
734 * @module ng
735 * @kind function
736 *
737 * @description
738 * Determines if a value is a date.
739 *
740 * @param {*} value Reference to check.
741 * @returns {boolean} True if `value` is a `Date`.
742 */
743function isDate(value) {
744 return toString.call(value) === '[object Date]';
745}
746
747
748/**
749 * @ngdoc function
750 * @name angular.isArray
751 * @module ng
752 * @kind function
753 *
754 * @description
755 * Determines if a reference is an `Array`.
756 *
757 * @param {*} value Reference to check.
758 * @returns {boolean} True if `value` is an `Array`.
759 */
760function isArray(arr) {
761 return Array.isArray(arr) || arr instanceof Array;
762}
763
764/**
765 * @description
766 * Determines if a reference is an `Error`.
767 * Loosely based on https://www.npmjs.com/package/iserror
768 *
769 * @param {*} value Reference to check.
770 * @returns {boolean} True if `value` is an `Error`.
771 */
772function isError(value) {
773 var tag = toString.call(value);
774 switch (tag) {
775 case '[object Error]': return true;
776 case '[object Exception]': return true;
777 case '[object DOMException]': return true;
778 default: return value instanceof Error;
779 }
780}
781
782/**
783 * @ngdoc function
784 * @name angular.isFunction
785 * @module ng
786 * @kind function
787 *
788 * @description
789 * Determines if a reference is a `Function`.
790 *
791 * @param {*} value Reference to check.
792 * @returns {boolean} True if `value` is a `Function`.
793 */
794function isFunction(value) {return typeof value === 'function';}
795
796
797/**
798 * Determines if a value is a regular expression object.
799 *
800 * @private
801 * @param {*} value Reference to check.
802 * @returns {boolean} True if `value` is a `RegExp`.
803 */
804function isRegExp(value) {
805 return toString.call(value) === '[object RegExp]';
806}
807
808
809/**
810 * Checks if `obj` is a window object.
811 *
812 * @private
813 * @param {*} obj Object to check
814 * @returns {boolean} True if `obj` is a window obj.
815 */
816function isWindow(obj) {
817 return obj && obj.window === obj;
818}
819
820
821function isScope(obj) {
822 return obj && obj.$evalAsync && obj.$watch;
823}
824
825
826function isFile(obj) {
827 return toString.call(obj) === '[object File]';
828}
829
830
831function isFormData(obj) {
832 return toString.call(obj) === '[object FormData]';
833}
834
835
836function isBlob(obj) {
837 return toString.call(obj) === '[object Blob]';
838}
839
840
841function isBoolean(value) {
842 return typeof value === 'boolean';
843}
844
845
846function isPromiseLike(obj) {
847 return obj && isFunction(obj.then);
848}
849
850
851var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/;
852function isTypedArray(value) {
853 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
854}
855
856function isArrayBuffer(obj) {
857 return toString.call(obj) === '[object ArrayBuffer]';
858}
859
860
861var trim = function(value) {
862 return isString(value) ? value.trim() : value;
863};
864
865// Copied from:
866// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
867// Prereq: s is a string.
868var escapeForRegexp = function(s) {
869 return s
870 .replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, '\\$1')
871 // eslint-disable-next-line no-control-regex
872 .replace(/\x08/g, '\\x08');
873};
874
875
876/**
877 * @ngdoc function
878 * @name angular.isElement
879 * @module ng
880 * @kind function
881 *
882 * @description
883 * Determines if a reference is a DOM element (or wrapped jQuery element).
884 *
885 * @param {*} value Reference to check.
886 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
887 */
888function isElement(node) {
889 return !!(node &&
890 (node.nodeName // We are a direct element.
891 || (node.prop && node.attr && node.find))); // We have an on and find method part of jQuery API.
892}
893
894/**
895 * @param str 'key1,key2,...'
896 * @returns {object} in the form of {key1:true, key2:true, ...}
897 */
898function makeMap(str) {
899 var obj = {}, items = str.split(','), i;
900 for (i = 0; i < items.length; i++) {
901 obj[items[i]] = true;
902 }
903 return obj;
904}
905
906
907function nodeName_(element) {
908 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
909}
910
911function includes(array, obj) {
912 return Array.prototype.indexOf.call(array, obj) !== -1;
913}
914
915function arrayRemove(array, value) {
916 var index = array.indexOf(value);
917 if (index >= 0) {
918 array.splice(index, 1);
919 }
920 return index;
921}
922
923/**
924 * @ngdoc function
925 * @name angular.copy
926 * @module ng
927 * @kind function
928 *
929 * @description
930 * Creates a deep copy of `source`, which should be an object or an array. This functions is used
931 * internally, mostly in the change-detection code. It is not intended as an all-purpose copy
932 * function, and has several limitations (see below).
933 *
934 * * If no destination is supplied, a copy of the object or array is created.
935 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
936 * are deleted and then all elements/properties from the source are copied to it.
937 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
938 * * If `source` is identical to `destination` an exception will be thrown.
939 *
940 * <br />
941 *
942 * <div class="alert alert-warning">
943 * Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
944 * and on `destination`) will be ignored.
945 * </div>
946 *
947 * <div class="alert alert-warning">
948 * `angular.copy` does not check if destination and source are of the same type. It's the
949 * developer's responsibility to make sure they are compatible.
950 * </div>
951 *
952 * @knownIssue
953 * This is a non-exhaustive list of object types / features that are not handled correctly by
954 * `angular.copy`. Note that since this functions is used by the change detection code, this
955 * means binding or watching objects of these types (or that include these types) might not work
956 * correctly.
957 * - [`File`](https://developer.mozilla.org/docs/Web/API/File)
958 * - [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)
959 * - [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData)
960 * - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream)
961 * - [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)
962 * - [`WeakMap`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
963 * - [`getter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/
964 * [`setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set)
965 *
966 * @param {*} source The source that will be used to make a copy. Can be any type, including
967 * primitives, `null`, and `undefined`.
968 * @param {(Object|Array)=} destination Destination into which the source is copied. If provided,
969 * must be of the same type as `source`.
970 * @returns {*} The copy or updated `destination`, if `destination` was specified.
971 *
972 * @example
973 <example module="copyExample" name="angular-copy">
974 <file name="index.html">
975 <div ng-controller="ExampleController">
976 <form novalidate class="simple-form">
977 <label>Name: <input type="text" ng-model="user.name" /></label><br />
978 <label>Age: <input type="number" ng-model="user.age" /></label><br />
979 Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>
980 <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />
981 <button ng-click="reset()">RESET</button>
982 <button ng-click="update(user)">SAVE</button>
983 </form>
984 <pre>form = {{user | json}}</pre>
985 <pre>leader = {{leader | json}}</pre>
986 </div>
987 </file>
988 <file name="script.js">
989 // Module: copyExample
990 angular.
991 module('copyExample', []).
992 controller('ExampleController', ['$scope', function($scope) {
993 $scope.leader = {};
994
995 $scope.reset = function() {
996 // Example with 1 argument
997 $scope.user = angular.copy($scope.leader);
998 };
999
1000 $scope.update = function(user) {
1001 // Example with 2 arguments
1002 angular.copy(user, $scope.leader);
1003 };
1004
1005 $scope.reset();
1006 }]);
1007 </file>
1008 </example>
1009 */
1010function copy(source, destination, maxDepth) {
1011 var stackSource = [];
1012 var stackDest = [];
1013 maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN;
1014
1015 if (destination) {
1016 if (isTypedArray(destination) || isArrayBuffer(destination)) {
1017 throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.');
1018 }
1019 if (source === destination) {
1020 throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.');
1021 }
1022
1023 // Empty the destination object
1024 if (isArray(destination)) {
1025 destination.length = 0;
1026 } else {
1027 forEach(destination, function(value, key) {
1028 if (key !== '$$hashKey') {
1029 delete destination[key];
1030 }
1031 });
1032 }
1033
1034 stackSource.push(source);
1035 stackDest.push(destination);
1036 return copyRecurse(source, destination, maxDepth);
1037 }
1038
1039 return copyElement(source, maxDepth);
1040
1041 function copyRecurse(source, destination, maxDepth) {
1042 maxDepth--;
1043 if (maxDepth < 0) {
1044 return '...';
1045 }
1046 var h = destination.$$hashKey;
1047 var key;
1048 if (isArray(source)) {
1049 for (var i = 0, ii = source.length; i < ii; i++) {
1050 destination.push(copyElement(source[i], maxDepth));
1051 }
1052 } else if (isBlankObject(source)) {
1053 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
1054 for (key in source) {
1055 destination[key] = copyElement(source[key], maxDepth);
1056 }
1057 } else if (source && typeof source.hasOwnProperty === 'function') {
1058 // Slow path, which must rely on hasOwnProperty
1059 for (key in source) {
1060 if (source.hasOwnProperty(key)) {
1061 destination[key] = copyElement(source[key], maxDepth);
1062 }
1063 }
1064 } else {
1065 // Slowest path --- hasOwnProperty can't be called as a method
1066 for (key in source) {
1067 if (hasOwnProperty.call(source, key)) {
1068 destination[key] = copyElement(source[key], maxDepth);
1069 }
1070 }
1071 }
1072 setHashKey(destination, h);
1073 return destination;
1074 }
1075
1076 function copyElement(source, maxDepth) {
1077 // Simple values
1078 if (!isObject(source)) {
1079 return source;
1080 }
1081
1082 // Already copied values
1083 var index = stackSource.indexOf(source);
1084 if (index !== -1) {
1085 return stackDest[index];
1086 }
1087
1088 if (isWindow(source) || isScope(source)) {
1089 throw ngMinErr('cpws',
1090 'Can\'t copy! Making copies of Window or Scope instances is not supported.');
1091 }
1092
1093 var needsRecurse = false;
1094 var destination = copyType(source);
1095
1096 if (destination === undefined) {
1097 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
1098 needsRecurse = true;
1099 }
1100
1101 stackSource.push(source);
1102 stackDest.push(destination);
1103
1104 return needsRecurse
1105 ? copyRecurse(source, destination, maxDepth)
1106 : destination;
1107 }
1108
1109 function copyType(source) {
1110 switch (toString.call(source)) {
1111 case '[object Int8Array]':
1112 case '[object Int16Array]':
1113 case '[object Int32Array]':
1114 case '[object Float32Array]':
1115 case '[object Float64Array]':
1116 case '[object Uint8Array]':
1117 case '[object Uint8ClampedArray]':
1118 case '[object Uint16Array]':
1119 case '[object Uint32Array]':
1120 return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length);
1121
1122 case '[object ArrayBuffer]':
1123 // Support: IE10
1124 if (!source.slice) {
1125 // If we're in this case we know the environment supports ArrayBuffer
1126 /* eslint-disable no-undef */
1127 var copied = new ArrayBuffer(source.byteLength);
1128 new Uint8Array(copied).set(new Uint8Array(source));
1129 /* eslint-enable */
1130 return copied;
1131 }
1132 return source.slice(0);
1133
1134 case '[object Boolean]':
1135 case '[object Number]':
1136 case '[object String]':
1137 case '[object Date]':
1138 return new source.constructor(source.valueOf());
1139
1140 case '[object RegExp]':
1141 var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]);
1142 re.lastIndex = source.lastIndex;
1143 return re;
1144
1145 case '[object Blob]':
1146 return new source.constructor([source], {type: source.type});
1147 }
1148
1149 if (isFunction(source.cloneNode)) {
1150 return source.cloneNode(true);
1151 }
1152 }
1153}
1154
1155
1156// eslint-disable-next-line no-self-compare
1157function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }
1158
1159
1160/**
1161 * @ngdoc function
1162 * @name angular.equals
1163 * @module ng
1164 * @kind function
1165 *
1166 * @description
1167 * Determines if two objects or two values are equivalent. Supports value types, regular
1168 * expressions, arrays and objects.
1169 *
1170 * Two objects or values are considered equivalent if at least one of the following is true:
1171 *
1172 * * Both objects or values pass `===` comparison.
1173 * * Both objects or values are of the same type and all of their properties are equal by
1174 * comparing them with `angular.equals`.
1175 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1176 * * Both values represent the same regular expression (In JavaScript,
1177 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1178 * representation matches).
1179 *
1180 * During a property comparison, properties of `function` type and properties with names
1181 * that begin with `$` are ignored.
1182 *
1183 * Scope and DOMWindow objects are being compared only by identify (`===`).
1184 *
1185 * @param {*} o1 Object or value to compare.
1186 * @param {*} o2 Object or value to compare.
1187 * @returns {boolean} True if arguments are equal.
1188 *
1189 * @example
1190 <example module="equalsExample" name="equalsExample">
1191 <file name="index.html">
1192 <div ng-controller="ExampleController">
1193 <form novalidate>
1194 <h3>User 1</h3>
1195 Name: <input type="text" ng-model="user1.name">
1196 Age: <input type="number" ng-model="user1.age">
1197
1198 <h3>User 2</h3>
1199 Name: <input type="text" ng-model="user2.name">
1200 Age: <input type="number" ng-model="user2.age">
1201
1202 <div>
1203 <br/>
1204 <input type="button" value="Compare" ng-click="compare()">
1205 </div>
1206 User 1: <pre>{{user1 | json}}</pre>
1207 User 2: <pre>{{user2 | json}}</pre>
1208 Equal: <pre>{{result}}</pre>
1209 </form>
1210 </div>
1211 </file>
1212 <file name="script.js">
1213 angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) {
1214 $scope.user1 = {};
1215 $scope.user2 = {};
1216 $scope.compare = function() {
1217 $scope.result = angular.equals($scope.user1, $scope.user2);
1218 };
1219 }]);
1220 </file>
1221 </example>
1222 */
1223function equals(o1, o2) {
1224 if (o1 === o2) return true;
1225 if (o1 === null || o2 === null) return false;
1226 // eslint-disable-next-line no-self-compare
1227 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1228 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1229 if (t1 === t2 && t1 === 'object') {
1230 if (isArray(o1)) {
1231 if (!isArray(o2)) return false;
1232 if ((length = o1.length) === o2.length) {
1233 for (key = 0; key < length; key++) {
1234 if (!equals(o1[key], o2[key])) return false;
1235 }
1236 return true;
1237 }
1238 } else if (isDate(o1)) {
1239 if (!isDate(o2)) return false;
1240 return simpleCompare(o1.getTime(), o2.getTime());
1241 } else if (isRegExp(o1)) {
1242 if (!isRegExp(o2)) return false;
1243 return o1.toString() === o2.toString();
1244 } else {
1245 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1246 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1247 keySet = createMap();
1248 for (key in o1) {
1249 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1250 if (!equals(o1[key], o2[key])) return false;
1251 keySet[key] = true;
1252 }
1253 for (key in o2) {
1254 if (!(key in keySet) &&
1255 key.charAt(0) !== '$' &&
1256 isDefined(o2[key]) &&
1257 !isFunction(o2[key])) return false;
1258 }
1259 return true;
1260 }
1261 }
1262 return false;
1263}
1264
1265var csp = function() {
1266 if (!isDefined(csp.rules)) {
1267
1268
1269 var ngCspElement = (window.document.querySelector('[ng-csp]') ||
1270 window.document.querySelector('[data-ng-csp]'));
1271
1272 if (ngCspElement) {
1273 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1274 ngCspElement.getAttribute('data-ng-csp');
1275 csp.rules = {
1276 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1277 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1278 };
1279 } else {
1280 csp.rules = {
1281 noUnsafeEval: noUnsafeEval(),
1282 noInlineStyle: false
1283 };
1284 }
1285 }
1286
1287 return csp.rules;
1288
1289 function noUnsafeEval() {
1290 try {
1291 // eslint-disable-next-line no-new, no-new-func
1292 new Function('');
1293 return false;
1294 } catch (e) {
1295 return true;
1296 }
1297 }
1298};
1299
1300/**
1301 * @ngdoc directive
1302 * @module ng
1303 * @name ngJq
1304 *
1305 * @element ANY
1306 * @param {string=} ngJq the name of the library available under `window`
1307 * to be used for angular.element
1308 * @description
1309 * Use this directive to force the angular.element library. This should be
1310 * used to force either jqLite by leaving ng-jq blank or setting the name of
1311 * the jquery variable under window (eg. jQuery).
1312 *
1313 * Since AngularJS looks for this directive when it is loaded (doesn't wait for the
1314 * DOMContentLoaded event), it must be placed on an element that comes before the script
1315 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1316 * others ignored.
1317 *
1318 * @example
1319 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1320 ```html
1321 <!doctype html>
1322 <html ng-app ng-jq>
1323 ...
1324 ...
1325 </html>
1326 ```
1327 * @example
1328 * This example shows how to use a jQuery based library of a different name.
1329 * The library name must be available at the top most 'window'.
1330 ```html
1331 <!doctype html>
1332 <html ng-app ng-jq="jQueryLib">
1333 ...
1334 ...
1335 </html>
1336 ```
1337 */
1338var jq = function() {
1339 if (isDefined(jq.name_)) return jq.name_;
1340 var el;
1341 var i, ii = ngAttrPrefixes.length, prefix, name;
1342 for (i = 0; i < ii; ++i) {
1343 prefix = ngAttrPrefixes[i];
1344 el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]');
1345 if (el) {
1346 name = el.getAttribute(prefix + 'jq');
1347 break;
1348 }
1349 }
1350
1351 return (jq.name_ = name);
1352};
1353
1354function concat(array1, array2, index) {
1355 return array1.concat(slice.call(array2, index));
1356}
1357
1358function sliceArgs(args, startIndex) {
1359 return slice.call(args, startIndex || 0);
1360}
1361
1362
1363/**
1364 * @ngdoc function
1365 * @name angular.bind
1366 * @module ng
1367 * @kind function
1368 *
1369 * @description
1370 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1371 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1372 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1373 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1374 *
1375 * @param {Object} self Context which `fn` should be evaluated in.
1376 * @param {function()} fn Function to be bound.
1377 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1378 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1379 */
1380function bind(self, fn) {
1381 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1382 if (isFunction(fn) && !(fn instanceof RegExp)) {
1383 return curryArgs.length
1384 ? function() {
1385 return arguments.length
1386 ? fn.apply(self, concat(curryArgs, arguments, 0))
1387 : fn.apply(self, curryArgs);
1388 }
1389 : function() {
1390 return arguments.length
1391 ? fn.apply(self, arguments)
1392 : fn.call(self);
1393 };
1394 } else {
1395 // In IE, native methods are not functions so they cannot be bound (note: they don't need to be).
1396 return fn;
1397 }
1398}
1399
1400
1401function toJsonReplacer(key, value) {
1402 var val = value;
1403
1404 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1405 val = undefined;
1406 } else if (isWindow(value)) {
1407 val = '$WINDOW';
1408 } else if (value && window.document === value) {
1409 val = '$DOCUMENT';
1410 } else if (isScope(value)) {
1411 val = '$SCOPE';
1412 }
1413
1414 return val;
1415}
1416
1417
1418/**
1419 * @ngdoc function
1420 * @name angular.toJson
1421 * @module ng
1422 * @kind function
1423 *
1424 * @description
1425 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1426 * stripped since AngularJS uses this notation internally.
1427 *
1428 * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON.
1429 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1430 * If set to an integer, the JSON output will contain that many spaces per indentation.
1431 * @returns {string|undefined} JSON-ified string representing `obj`.
1432 * @knownIssue
1433 *
1434 * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date`
1435 * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the
1436 * `Date.prototype.toJSON` method as follows:
1437 *
1438 * ```
1439 * var _DatetoJSON = Date.prototype.toJSON;
1440 * Date.prototype.toJSON = function() {
1441 * try {
1442 * return _DatetoJSON.call(this);
1443 * } catch(e) {
1444 * if (e instanceof RangeError) {
1445 * return null;
1446 * }
1447 * throw e;
1448 * }
1449 * };
1450 * ```
1451 *
1452 * See https://github.com/angular/angular.js/pull/14221 for more information.
1453 */
1454function toJson(obj, pretty) {
1455 if (isUndefined(obj)) return undefined;
1456 if (!isNumber(pretty)) {
1457 pretty = pretty ? 2 : null;
1458 }
1459 return JSON.stringify(obj, toJsonReplacer, pretty);
1460}
1461
1462
1463/**
1464 * @ngdoc function
1465 * @name angular.fromJson
1466 * @module ng
1467 * @kind function
1468 *
1469 * @description
1470 * Deserializes a JSON string.
1471 *
1472 * @param {string} json JSON string to deserialize.
1473 * @returns {Object|Array|string|number} Deserialized JSON string.
1474 */
1475function fromJson(json) {
1476 return isString(json)
1477 ? JSON.parse(json)
1478 : json;
1479}
1480
1481
1482var ALL_COLONS = /:/g;
1483function timezoneToOffset(timezone, fallback) {
1484 // Support: IE 9-11 only, Edge 13-15+
1485 // IE/Edge do not "understand" colon (`:`) in timezone
1486 timezone = timezone.replace(ALL_COLONS, '');
1487 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1488 return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1489}
1490
1491
1492function addDateMinutes(date, minutes) {
1493 date = new Date(date.getTime());
1494 date.setMinutes(date.getMinutes() + minutes);
1495 return date;
1496}
1497
1498
1499function convertTimezoneToLocal(date, timezone, reverse) {
1500 reverse = reverse ? -1 : 1;
1501 var dateTimezoneOffset = date.getTimezoneOffset();
1502 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
1503 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
1504}
1505
1506
1507/**
1508 * @returns {string} Returns the string representation of the element.
1509 */
1510function startingTag(element) {
1511 element = jqLite(element).clone().empty();
1512 var elemHtml = jqLite('<div></div>').append(element).html();
1513 try {
1514 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1515 elemHtml.
1516 match(/^(<[^>]+>)/)[1].
1517 replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);});
1518 } catch (e) {
1519 return lowercase(elemHtml);
1520 }
1521
1522}
1523
1524
1525/////////////////////////////////////////////////
1526
1527/**
1528 * Tries to decode the URI component without throwing an exception.
1529 *
1530 * @private
1531 * @param str value potential URI component to check.
1532 * @returns {boolean} True if `value` can be decoded
1533 * with the decodeURIComponent function.
1534 */
1535function tryDecodeURIComponent(value) {
1536 try {
1537 return decodeURIComponent(value);
1538 } catch (e) {
1539 // Ignore any invalid uri component.
1540 }
1541}
1542
1543
1544/**
1545 * Parses an escaped url query string into key-value pairs.
1546 * @returns {Object.<string,boolean|Array>}
1547 */
1548function parseKeyValue(/**string*/keyValue) {
1549 var obj = {};
1550 forEach((keyValue || '').split('&'), function(keyValue) {
1551 var splitPoint, key, val;
1552 if (keyValue) {
1553 key = keyValue = keyValue.replace(/\+/g,'%20');
1554 splitPoint = keyValue.indexOf('=');
1555 if (splitPoint !== -1) {
1556 key = keyValue.substring(0, splitPoint);
1557 val = keyValue.substring(splitPoint + 1);
1558 }
1559 key = tryDecodeURIComponent(key);
1560 if (isDefined(key)) {
1561 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1562 if (!hasOwnProperty.call(obj, key)) {
1563 obj[key] = val;
1564 } else if (isArray(obj[key])) {
1565 obj[key].push(val);
1566 } else {
1567 obj[key] = [obj[key],val];
1568 }
1569 }
1570 }
1571 });
1572 return obj;
1573}
1574
1575function toKeyValue(obj) {
1576 var parts = [];
1577 forEach(obj, function(value, key) {
1578 if (isArray(value)) {
1579 forEach(value, function(arrayValue) {
1580 parts.push(encodeUriQuery(key, true) +
1581 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1582 });
1583 } else {
1584 parts.push(encodeUriQuery(key, true) +
1585 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1586 }
1587 });
1588 return parts.length ? parts.join('&') : '';
1589}
1590
1591
1592/**
1593 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1594 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1595 * segments:
1596 * segment = *pchar
1597 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1598 * pct-encoded = "%" HEXDIG HEXDIG
1599 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1600 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1601 * / "*" / "+" / "," / ";" / "="
1602 */
1603function encodeUriSegment(val) {
1604 return encodeUriQuery(val, true).
1605 replace(/%26/gi, '&').
1606 replace(/%3D/gi, '=').
1607 replace(/%2B/gi, '+');
1608}
1609
1610
1611/**
1612 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1613 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1614 * encoded per http://tools.ietf.org/html/rfc3986:
1615 * query = *( pchar / "/" / "?" )
1616 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1617 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1618 * pct-encoded = "%" HEXDIG HEXDIG
1619 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1620 * / "*" / "+" / "," / ";" / "="
1621 */
1622function encodeUriQuery(val, pctEncodeSpaces) {
1623 return encodeURIComponent(val).
1624 replace(/%40/gi, '@').
1625 replace(/%3A/gi, ':').
1626 replace(/%24/g, '$').
1627 replace(/%2C/gi, ',').
1628 replace(/%3B/gi, ';').
1629 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1630}
1631
1632var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1633
1634function getNgAttribute(element, ngAttr) {
1635 var attr, i, ii = ngAttrPrefixes.length;
1636 for (i = 0; i < ii; ++i) {
1637 attr = ngAttrPrefixes[i] + ngAttr;
1638 if (isString(attr = element.getAttribute(attr))) {
1639 return attr;
1640 }
1641 }
1642 return null;
1643}
1644
1645function allowAutoBootstrap(document) {
1646 var script = document.currentScript;
1647
1648 if (!script) {
1649 // Support: IE 9-11 only
1650 // IE does not have `document.currentScript`
1651 return true;
1652 }
1653
1654 // If the `currentScript` property has been clobbered just return false, since this indicates a probable attack
1655 if (!(script instanceof window.HTMLScriptElement || script instanceof window.SVGScriptElement)) {
1656 return false;
1657 }
1658
1659 var attributes = script.attributes;
1660 var srcs = [attributes.getNamedItem('src'), attributes.getNamedItem('href'), attributes.getNamedItem('xlink:href')];
1661
1662 return srcs.every(function(src) {
1663 if (!src) {
1664 return true;
1665 }
1666 if (!src.value) {
1667 return false;
1668 }
1669
1670 var link = document.createElement('a');
1671 link.href = src.value;
1672
1673 if (document.location.origin === link.origin) {
1674 // Same-origin resources are always allowed, even for banned URL schemes.
1675 return true;
1676 }
1677 // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web.
1678 // This is to prevent angular.js bundled with browser extensions from being used to bypass the
1679 // content security policy in web pages and other browser extensions.
1680 switch (link.protocol) {
1681 case 'http:':
1682 case 'https:':
1683 case 'ftp:':
1684 case 'blob:':
1685 case 'file:':
1686 case 'data:':
1687 return true;
1688 default:
1689 return false;
1690 }
1691 });
1692}
1693
1694// Cached as it has to run during loading so that document.currentScript is available.
1695var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);
1696
1697/**
1698 * @ngdoc directive
1699 * @name ngApp
1700 * @module ng
1701 *
1702 * @element ANY
1703 * @param {angular.Module} ngApp an optional application
1704 * {@link angular.module module} name to load.
1705 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1706 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1707 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1708 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1709 * tracking down the root of these bugs.
1710 *
1711 * @description
1712 *
1713 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1714 * designates the **root element** of the application and is typically placed near the root element
1715 * of the page - e.g. on the `<body>` or `<html>` tags.
1716 *
1717 * There are a few things to keep in mind when using `ngApp`:
1718 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1719 * found in the document will be used to define the root element to auto-bootstrap as an
1720 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1721 * {@link angular.bootstrap} instead.
1722 * - AngularJS applications cannot be nested within each other.
1723 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`.
1724 * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and
1725 * {@link ngRoute.ngView `ngView`}.
1726 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
1727 * causing animations to stop working and making the injector inaccessible from outside the app.
1728 *
1729 * You can specify an **AngularJS module** to be used as the root module for the application. This
1730 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1731 * should contain the application code needed or have dependencies on other modules that will
1732 * contain the code. See {@link angular.module} for more information.
1733 *
1734 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1735 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1736 * would not be resolved to `3`.
1737 *
1738 * @example
1739 *
1740 * ### Simple Usage
1741 *
1742 * `ngApp` is the easiest, and most common way to bootstrap an application.
1743 *
1744 <example module="ngAppDemo" name="ng-app">
1745 <file name="index.html">
1746 <div ng-controller="ngAppDemoController">
1747 I can add: {{a}} + {{b}} = {{ a+b }}
1748 </div>
1749 </file>
1750 <file name="script.js">
1751 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1752 $scope.a = 1;
1753 $scope.b = 2;
1754 });
1755 </file>
1756 </example>
1757 *
1758 * @example
1759 *
1760 * ### With `ngStrictDi`
1761 *
1762 * Using `ngStrictDi`, you would see something like this:
1763 *
1764 <example ng-app-included="true" name="strict-di">
1765 <file name="index.html">
1766 <div ng-app="ngAppStrictDemo" ng-strict-di>
1767 <div ng-controller="GoodController1">
1768 I can add: {{a}} + {{b}} = {{ a+b }}
1769
1770 <p>This renders because the controller does not fail to
1771 instantiate, by using explicit annotation style (see
1772 script.js for details)
1773 </p>
1774 </div>
1775
1776 <div ng-controller="GoodController2">
1777 Name: <input ng-model="name"><br />
1778 Hello, {{name}}!
1779
1780 <p>This renders because the controller does not fail to
1781 instantiate, by using explicit annotation style
1782 (see script.js for details)
1783 </p>
1784 </div>
1785
1786 <div ng-controller="BadController">
1787 I can add: {{a}} + {{b}} = {{ a+b }}
1788
1789 <p>The controller could not be instantiated, due to relying
1790 on automatic function annotations (which are disabled in
1791 strict mode). As such, the content of this section is not
1792 interpolated, and there should be an error in your web console.
1793 </p>
1794 </div>
1795 </div>
1796 </file>
1797 <file name="script.js">
1798 angular.module('ngAppStrictDemo', [])
1799 // BadController will fail to instantiate, due to relying on automatic function annotation,
1800 // rather than an explicit annotation
1801 .controller('BadController', function($scope) {
1802 $scope.a = 1;
1803 $scope.b = 2;
1804 })
1805 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1806 // due to using explicit annotations using the array style and $inject property, respectively.
1807 .controller('GoodController1', ['$scope', function($scope) {
1808 $scope.a = 1;
1809 $scope.b = 2;
1810 }])
1811 .controller('GoodController2', GoodController2);
1812 function GoodController2($scope) {
1813 $scope.name = 'World';
1814 }
1815 GoodController2.$inject = ['$scope'];
1816 </file>
1817 <file name="style.css">
1818 div[ng-controller] {
1819 margin-bottom: 1em;
1820 -webkit-border-radius: 4px;
1821 border-radius: 4px;
1822 border: 1px solid;
1823 padding: .5em;
1824 }
1825 div[ng-controller^=Good] {
1826 border-color: #d6e9c6;
1827 background-color: #dff0d8;
1828 color: #3c763d;
1829 }
1830 div[ng-controller^=Bad] {
1831 border-color: #ebccd1;
1832 background-color: #f2dede;
1833 color: #a94442;
1834 margin-bottom: 0;
1835 }
1836 </file>
1837 </example>
1838 */
1839function angularInit(element, bootstrap) {
1840 var appElement,
1841 module,
1842 config = {};
1843
1844 // The element `element` has priority over any other element.
1845 forEach(ngAttrPrefixes, function(prefix) {
1846 var name = prefix + 'app';
1847
1848 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1849 appElement = element;
1850 module = element.getAttribute(name);
1851 }
1852 });
1853 forEach(ngAttrPrefixes, function(prefix) {
1854 var name = prefix + 'app';
1855 var candidate;
1856
1857 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1858 appElement = candidate;
1859 module = candidate.getAttribute(name);
1860 }
1861 });
1862 if (appElement) {
1863 if (!isAutoBootstrapAllowed) {
1864 window.console.error('AngularJS: disabling automatic bootstrap. <script> protocol indicates ' +
1865 'an extension, document.location.href does not match.');
1866 return;
1867 }
1868 config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
1869 bootstrap(appElement, module ? [module] : [], config);
1870 }
1871}
1872
1873/**
1874 * @ngdoc function
1875 * @name angular.bootstrap
1876 * @module ng
1877 * @description
1878 * Use this function to manually start up AngularJS application.
1879 *
1880 * For more information, see the {@link guide/bootstrap Bootstrap guide}.
1881 *
1882 * AngularJS will detect if it has been loaded into the browser more than once and only allow the
1883 * first loaded script to be bootstrapped and will report a warning to the browser console for
1884 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1885 * multiple instances of AngularJS try to work on the DOM.
1886 *
1887 * <div class="alert alert-warning">
1888 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually.
1889 * They must use {@link ng.directive:ngApp ngApp}.
1890 * </div>
1891 *
1892 * <div class="alert alert-warning">
1893 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion},
1894 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}.
1895 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
1896 * causing animations to stop working and making the injector inaccessible from outside the app.
1897 * </div>
1898 *
1899 * ```html
1900 * <!doctype html>
1901 * <html>
1902 * <body>
1903 * <div ng-controller="WelcomeController">
1904 * {{greeting}}
1905 * </div>
1906 *
1907 * <script src="angular.js"></script>
1908 * <script>
1909 * var app = angular.module('demo', [])
1910 * .controller('WelcomeController', function($scope) {
1911 * $scope.greeting = 'Welcome!';
1912 * });
1913 * angular.bootstrap(document, ['demo']);
1914 * </script>
1915 * </body>
1916 * </html>
1917 * ```
1918 *
1919 * @param {DOMElement} element DOM element which is the root of AngularJS application.
1920 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1921 * Each item in the array should be the name of a predefined module or a (DI annotated)
1922 * function that will be invoked by the injector as a `config` block.
1923 * See: {@link angular.module modules}
1924 * @param {Object=} config an object for defining configuration options for the application. The
1925 * following keys are supported:
1926 *
1927 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1928 * assist in finding bugs which break minified code. Defaults to `false`.
1929 *
1930 * @returns {auto.$injector} Returns the newly created injector for this app.
1931 */
1932function bootstrap(element, modules, config) {
1933 if (!isObject(config)) config = {};
1934 var defaultConfig = {
1935 strictDi: false
1936 };
1937 config = extend(defaultConfig, config);
1938 var doBootstrap = function() {
1939 element = jqLite(element);
1940
1941 if (element.injector()) {
1942 var tag = (element[0] === window.document) ? 'document' : startingTag(element);
1943 // Encode angle brackets to prevent input from being sanitized to empty string #8683.
1944 throw ngMinErr(
1945 'btstrpd',
1946 'App already bootstrapped with this element \'{0}\'',
1947 tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
1948 }
1949
1950 modules = modules || [];
1951 modules.unshift(['$provide', function($provide) {
1952 $provide.value('$rootElement', element);
1953 }]);
1954
1955 if (config.debugInfoEnabled) {
1956 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1957 modules.push(['$compileProvider', function($compileProvider) {
1958 $compileProvider.debugInfoEnabled(true);
1959 }]);
1960 }
1961
1962 modules.unshift('ng');
1963 var injector = createInjector(modules, config.strictDi);
1964 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1965 function bootstrapApply(scope, element, compile, injector) {
1966 scope.$apply(function() {
1967 element.data('$injector', injector);
1968 compile(element)(scope);
1969 });
1970 }]
1971 );
1972 return injector;
1973 };
1974
1975 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1976 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1977
1978 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1979 config.debugInfoEnabled = true;
1980 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1981 }
1982
1983 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1984 return doBootstrap();
1985 }
1986
1987 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1988 angular.resumeBootstrap = function(extraModules) {
1989 forEach(extraModules, function(module) {
1990 modules.push(module);
1991 });
1992 return doBootstrap();
1993 };
1994
1995 if (isFunction(angular.resumeDeferredBootstrap)) {
1996 angular.resumeDeferredBootstrap();
1997 }
1998}
1999
2000/**
2001 * @ngdoc function
2002 * @name angular.reloadWithDebugInfo
2003 * @module ng
2004 * @description
2005 * Use this function to reload the current application with debug information turned on.
2006 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
2007 *
2008 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
2009 */
2010function reloadWithDebugInfo() {
2011 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
2012 window.location.reload();
2013}
2014
2015/**
2016 * @name angular.getTestability
2017 * @module ng
2018 * @description
2019 * Get the testability service for the instance of AngularJS on the given
2020 * element.
2021 * @param {DOMElement} element DOM element which is the root of AngularJS application.
2022 */
2023function getTestability(rootElement) {
2024 var injector = angular.element(rootElement).injector();
2025 if (!injector) {
2026 throw ngMinErr('test',
2027 'no injector found for element argument to getTestability');
2028 }
2029 return injector.get('$$testability');
2030}
2031
2032var SNAKE_CASE_REGEXP = /[A-Z]/g;
2033function snake_case(name, separator) {
2034 separator = separator || '_';
2035 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
2036 return (pos ? separator : '') + letter.toLowerCase();
2037 });
2038}
2039
2040var bindJQueryFired = false;
2041function bindJQuery() {
2042 var originalCleanData;
2043
2044 if (bindJQueryFired) {
2045 return;
2046 }
2047
2048 // bind to jQuery if present;
2049 var jqName = jq();
2050 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
2051 !jqName ? undefined : // use jqLite
2052 window[jqName]; // use jQuery specified by `ngJq`
2053
2054 // Use jQuery if it exists with proper functionality, otherwise default to us.
2055 // AngularJS 1.2+ requires jQuery 1.7+ for on()/off() support.
2056 // AngularJS 1.3+ technically requires at least jQuery 2.1+ but it may work with older
2057 // versions. It will not work for sure with jQuery <1.7, though.
2058 if (jQuery && jQuery.fn.on) {
2059 jqLite = jQuery;
2060 extend(jQuery.fn, {
2061 scope: JQLitePrototype.scope,
2062 isolateScope: JQLitePrototype.isolateScope,
2063 controller: /** @type {?} */ (JQLitePrototype).controller,
2064 injector: JQLitePrototype.injector,
2065 inheritedData: JQLitePrototype.inheritedData
2066 });
2067 } else {
2068 jqLite = JQLite;
2069 }
2070
2071 // All nodes removed from the DOM via various jqLite/jQuery APIs like .remove()
2072 // are passed through jqLite/jQuery.cleanData. Monkey-patch this method to fire
2073 // the $destroy event on all removed nodes.
2074 originalCleanData = jqLite.cleanData;
2075 jqLite.cleanData = function(elems) {
2076 var events;
2077 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
2078 events = (jqLite._data(elem) || {}).events;
2079 if (events && events.$destroy) {
2080 jqLite(elem).triggerHandler('$destroy');
2081 }
2082 }
2083 originalCleanData(elems);
2084 };
2085
2086 angular.element = jqLite;
2087
2088 // Prevent double-proxying.
2089 bindJQueryFired = true;
2090}
2091
2092/**
2093 * @ngdoc function
2094 * @name angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement
2095 * @module ng
2096 * @kind function
2097 *
2098 * @description
2099 * Restores the pre-1.8 behavior of jqLite that turns XHTML-like strings like
2100 * `<div /><span />` to `<div></div><span></span>` instead of `<div><span></span></div>`.
2101 * The new behavior is a security fix. Thus, if you need to call this function, please try to adjust
2102 * your code for this change and remove your use of this function as soon as possible.
2103
2104 * Note that this only patches jqLite. If you use jQuery 3.5.0 or newer, please read the
2105 * [jQuery 3.5 upgrade guide](https://jquery.com/upgrade-guide/3.5/) for more details
2106 * about the workarounds.
2107 */
2108function UNSAFE_restoreLegacyJqLiteXHTMLReplacement() {
2109 JQLite.legacyXHTMLReplacement = true;
2110}
2111
2112/**
2113 * throw error if the argument is falsy.
2114 */
2115function assertArg(arg, name, reason) {
2116 if (!arg) {
2117 throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required'));
2118 }
2119 return arg;
2120}
2121
2122function assertArgFn(arg, name, acceptArrayAnnotation) {
2123 if (acceptArrayAnnotation && isArray(arg)) {
2124 arg = arg[arg.length - 1];
2125 }
2126
2127 assertArg(isFunction(arg), name, 'not a function, got ' +
2128 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
2129 return arg;
2130}
2131
2132/**
2133 * throw error if the name given is hasOwnProperty
2134 * @param {String} name the name to test
2135 * @param {String} context the context in which the name is used, such as module or directive
2136 */
2137function assertNotHasOwnProperty(name, context) {
2138 if (name === 'hasOwnProperty') {
2139 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2140 }
2141}
2142
2143/**
2144 * Return the value accessible from the object by path. Any undefined traversals are ignored
2145 * @param {Object} obj starting object
2146 * @param {String} path path to traverse
2147 * @param {boolean} [bindFnToScope=true]
2148 * @returns {Object} value as accessible by path
2149 */
2150//TODO(misko): this function needs to be removed
2151function getter(obj, path, bindFnToScope) {
2152 if (!path) return obj;
2153 var keys = path.split('.');
2154 var key;
2155 var lastInstance = obj;
2156 var len = keys.length;
2157
2158 for (var i = 0; i < len; i++) {
2159 key = keys[i];
2160 if (obj) {
2161 obj = (lastInstance = obj)[key];
2162 }
2163 }
2164 if (!bindFnToScope && isFunction(obj)) {
2165 return bind(lastInstance, obj);
2166 }
2167 return obj;
2168}
2169
2170/**
2171 * Return the DOM siblings between the first and last node in the given array.
2172 * @param {Array} array like object
2173 * @returns {Array} the inputted object or a jqLite collection containing the nodes
2174 */
2175function getBlockNodes(nodes) {
2176 // TODO(perf): update `nodes` instead of creating a new object?
2177 var node = nodes[0];
2178 var endNode = nodes[nodes.length - 1];
2179 var blockNodes;
2180
2181 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
2182 if (blockNodes || nodes[i] !== node) {
2183 if (!blockNodes) {
2184 blockNodes = jqLite(slice.call(nodes, 0, i));
2185 }
2186 blockNodes.push(node);
2187 }
2188 }
2189
2190 return blockNodes || nodes;
2191}
2192
2193
2194/**
2195 * Creates a new object without a prototype. This object is useful for lookup without having to
2196 * guard against prototypically inherited properties via hasOwnProperty.
2197 *
2198 * Related micro-benchmarks:
2199 * - http://jsperf.com/object-create2
2200 * - http://jsperf.com/proto-map-lookup/2
2201 * - http://jsperf.com/for-in-vs-object-keys2
2202 *
2203 * @returns {Object}
2204 */
2205function createMap() {
2206 return Object.create(null);
2207}
2208
2209function stringify(value) {
2210 if (value == null) { // null || undefined
2211 return '';
2212 }
2213 switch (typeof value) {
2214 case 'string':
2215 break;
2216 case 'number':
2217 value = '' + value;
2218 break;
2219 default:
2220 if (hasCustomToString(value) && !isArray(value) && !isDate(value)) {
2221 value = value.toString();
2222 } else {
2223 value = toJson(value);
2224 }
2225 }
2226
2227 return value;
2228}
2229
2230var NODE_TYPE_ELEMENT = 1;
2231var NODE_TYPE_ATTRIBUTE = 2;
2232var NODE_TYPE_TEXT = 3;
2233var NODE_TYPE_COMMENT = 8;
2234var NODE_TYPE_DOCUMENT = 9;
2235var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
2236
2237/**
2238 * @ngdoc type
2239 * @name angular.Module
2240 * @module ng
2241 * @description
2242 *
2243 * Interface for configuring AngularJS {@link angular.module modules}.
2244 */
2245
2246function setupModuleLoader(window) {
2247
2248 var $injectorMinErr = minErr('$injector');
2249 var ngMinErr = minErr('ng');
2250
2251 function ensure(obj, name, factory) {
2252 return obj[name] || (obj[name] = factory());
2253 }
2254
2255 var angular = ensure(window, 'angular', Object);
2256
2257 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2258 angular.$$minErr = angular.$$minErr || minErr;
2259
2260 return ensure(angular, 'module', function() {
2261 /** @type {Object.<string, angular.Module>} */
2262 var modules = {};
2263
2264 /**
2265 * @ngdoc function
2266 * @name angular.module
2267 * @module ng
2268 * @description
2269 *
2270 * The `angular.module` is a global place for creating, registering and retrieving AngularJS
2271 * modules.
2272 * All modules (AngularJS core or 3rd party) that should be available to an application must be
2273 * registered using this mechanism.
2274 *
2275 * Passing one argument retrieves an existing {@link angular.Module},
2276 * whereas passing more than one argument creates a new {@link angular.Module}
2277 *
2278 *
2279 * # Module
2280 *
2281 * A module is a collection of services, directives, controllers, filters, and configuration information.
2282 * `angular.module` is used to configure the {@link auto.$injector $injector}.
2283 *
2284 * ```js
2285 * // Create a new module
2286 * var myModule = angular.module('myModule', []);
2287 *
2288 * // register a new service
2289 * myModule.value('appName', 'MyCoolApp');
2290 *
2291 * // configure existing services inside initialization blocks.
2292 * myModule.config(['$locationProvider', function($locationProvider) {
2293 * // Configure existing providers
2294 * $locationProvider.hashPrefix('!');
2295 * }]);
2296 * ```
2297 *
2298 * Then you can create an injector and load your modules like this:
2299 *
2300 * ```js
2301 * var injector = angular.injector(['ng', 'myModule'])
2302 * ```
2303 *
2304 * However it's more likely that you'll just use
2305 * {@link ng.directive:ngApp ngApp} or
2306 * {@link angular.bootstrap} to simplify this process for you.
2307 *
2308 * @param {!string} name The name of the module to create or retrieve.
2309 * @param {!Array.<string>=} requires If specified then new module is being created. If
2310 * unspecified then the module is being retrieved for further configuration.
2311 * @param {Function=} configFn Optional configuration function for the module. Same as
2312 * {@link angular.Module#config Module#config()}.
2313 * @returns {angular.Module} new module with the {@link angular.Module} api.
2314 */
2315 return function module(name, requires, configFn) {
2316
2317 var info = {};
2318
2319 var assertNotHasOwnProperty = function(name, context) {
2320 if (name === 'hasOwnProperty') {
2321 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2322 }
2323 };
2324
2325 assertNotHasOwnProperty(name, 'module');
2326 if (requires && modules.hasOwnProperty(name)) {
2327 modules[name] = null;
2328 }
2329 return ensure(modules, name, function() {
2330 if (!requires) {
2331 throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' +
2332 'the module name or forgot to load it. If registering a module ensure that you ' +
2333 'specify the dependencies as the second argument.', name);
2334 }
2335
2336 /** @type {!Array.<Array.<*>>} */
2337 var invokeQueue = [];
2338
2339 /** @type {!Array.<Function>} */
2340 var configBlocks = [];
2341
2342 /** @type {!Array.<Function>} */
2343 var runBlocks = [];
2344
2345 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2346
2347 /** @type {angular.Module} */
2348 var moduleInstance = {
2349 // Private state
2350 _invokeQueue: invokeQueue,
2351 _configBlocks: configBlocks,
2352 _runBlocks: runBlocks,
2353
2354 /**
2355 * @ngdoc method
2356 * @name angular.Module#info
2357 * @module ng
2358 *
2359 * @param {Object=} info Information about the module
2360 * @returns {Object|Module} The current info object for this module if called as a getter,
2361 * or `this` if called as a setter.
2362 *
2363 * @description
2364 * Read and write custom information about this module.
2365 * For example you could put the version of the module in here.
2366 *
2367 * ```js
2368 * angular.module('myModule', []).info({ version: '1.0.0' });
2369 * ```
2370 *
2371 * The version could then be read back out by accessing the module elsewhere:
2372 *
2373 * ```
2374 * var version = angular.module('myModule').info().version;
2375 * ```
2376 *
2377 * You can also retrieve this information during runtime via the
2378 * {@link $injector#modules `$injector.modules`} property:
2379 *
2380 * ```js
2381 * var version = $injector.modules['myModule'].info().version;
2382 * ```
2383 */
2384 info: function(value) {
2385 if (isDefined(value)) {
2386 if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value');
2387 info = value;
2388 return this;
2389 }
2390 return info;
2391 },
2392
2393 /**
2394 * @ngdoc property
2395 * @name angular.Module#requires
2396 * @module ng
2397 *
2398 * @description
2399 * Holds the list of modules which the injector will load before the current module is
2400 * loaded.
2401 */
2402 requires: requires,
2403
2404 /**
2405 * @ngdoc property
2406 * @name angular.Module#name
2407 * @module ng
2408 *
2409 * @description
2410 * Name of the module.
2411 */
2412 name: name,
2413
2414
2415 /**
2416 * @ngdoc method
2417 * @name angular.Module#provider
2418 * @module ng
2419 * @param {string} name service name
2420 * @param {Function} providerType Construction function for creating new instance of the
2421 * service.
2422 * @description
2423 * See {@link auto.$provide#provider $provide.provider()}.
2424 */
2425 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2426
2427 /**
2428 * @ngdoc method
2429 * @name angular.Module#factory
2430 * @module ng
2431 * @param {string} name service name
2432 * @param {Function} providerFunction Function for creating new instance of the service.
2433 * @description
2434 * See {@link auto.$provide#factory $provide.factory()}.
2435 */
2436 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2437
2438 /**
2439 * @ngdoc method
2440 * @name angular.Module#service
2441 * @module ng
2442 * @param {string} name service name
2443 * @param {Function} constructor A constructor function that will be instantiated.
2444 * @description
2445 * See {@link auto.$provide#service $provide.service()}.
2446 */
2447 service: invokeLaterAndSetModuleName('$provide', 'service'),
2448
2449 /**
2450 * @ngdoc method
2451 * @name angular.Module#value
2452 * @module ng
2453 * @param {string} name service name
2454 * @param {*} object Service instance object.
2455 * @description
2456 * See {@link auto.$provide#value $provide.value()}.
2457 */
2458 value: invokeLater('$provide', 'value'),
2459
2460 /**
2461 * @ngdoc method
2462 * @name angular.Module#constant
2463 * @module ng
2464 * @param {string} name constant name
2465 * @param {*} object Constant value.
2466 * @description
2467 * Because the constants are fixed, they get applied before other provide methods.
2468 * See {@link auto.$provide#constant $provide.constant()}.
2469 */
2470 constant: invokeLater('$provide', 'constant', 'unshift'),
2471
2472 /**
2473 * @ngdoc method
2474 * @name angular.Module#decorator
2475 * @module ng
2476 * @param {string} name The name of the service to decorate.
2477 * @param {Function} decorFn This function will be invoked when the service needs to be
2478 * instantiated and should return the decorated service instance.
2479 * @description
2480 * See {@link auto.$provide#decorator $provide.decorator()}.
2481 */
2482 decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks),
2483
2484 /**
2485 * @ngdoc method
2486 * @name angular.Module#animation
2487 * @module ng
2488 * @param {string} name animation name
2489 * @param {Function} animationFactory Factory function for creating new instance of an
2490 * animation.
2491 * @description
2492 *
2493 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2494 *
2495 *
2496 * Defines an animation hook that can be later used with
2497 * {@link $animate $animate} service and directives that use this service.
2498 *
2499 * ```js
2500 * module.animation('.animation-name', function($inject1, $inject2) {
2501 * return {
2502 * eventName : function(element, done) {
2503 * //code to run the animation
2504 * //once complete, then run done()
2505 * return function cancellationFunction(element) {
2506 * //code to cancel the animation
2507 * }
2508 * }
2509 * }
2510 * })
2511 * ```
2512 *
2513 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2514 * {@link ngAnimate ngAnimate module} for more information.
2515 */
2516 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2517
2518 /**
2519 * @ngdoc method
2520 * @name angular.Module#filter
2521 * @module ng
2522 * @param {string} name Filter name - this must be a valid AngularJS expression identifier
2523 * @param {Function} filterFactory Factory function for creating new instance of filter.
2524 * @description
2525 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2526 *
2527 * <div class="alert alert-warning">
2528 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`.
2529 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2530 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2531 * (`myapp_subsection_filterx`).
2532 * </div>
2533 */
2534 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2535
2536 /**
2537 * @ngdoc method
2538 * @name angular.Module#controller
2539 * @module ng
2540 * @param {string|Object} name Controller name, or an object map of controllers where the
2541 * keys are the names and the values are the constructors.
2542 * @param {Function} constructor Controller constructor function.
2543 * @description
2544 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2545 */
2546 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2547
2548 /**
2549 * @ngdoc method
2550 * @name angular.Module#directive
2551 * @module ng
2552 * @param {string|Object} name Directive name, or an object map of directives where the
2553 * keys are the names and the values are the factories.
2554 * @param {Function} directiveFactory Factory function for creating new instance of
2555 * directives.
2556 * @description
2557 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2558 */
2559 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2560
2561 /**
2562 * @ngdoc method
2563 * @name angular.Module#component
2564 * @module ng
2565 * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
2566 * or an object map of components where the keys are the names and the values are the component definition objects.
2567 * @param {Object} options Component definition object (a simplified
2568 * {@link ng.$compile#directive-definition-object directive definition object})
2569 *
2570 * @description
2571 * See {@link ng.$compileProvider#component $compileProvider.component()}.
2572 */
2573 component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
2574
2575 /**
2576 * @ngdoc method
2577 * @name angular.Module#config
2578 * @module ng
2579 * @param {Function} configFn Execute this function on module load. Useful for service
2580 * configuration.
2581 * @description
2582 * Use this method to configure services by injecting their
2583 * {@link angular.Module#provider `providers`}, e.g. for adding routes to the
2584 * {@link ngRoute.$routeProvider $routeProvider}.
2585 *
2586 * Note that you can only inject {@link angular.Module#provider `providers`} and
2587 * {@link angular.Module#constant `constants`} into this function.
2588 *
2589 * For more about how to configure services, see
2590 * {@link providers#provider-recipe Provider Recipe}.
2591 */
2592 config: config,
2593
2594 /**
2595 * @ngdoc method
2596 * @name angular.Module#run
2597 * @module ng
2598 * @param {Function} initializationFn Execute this function after injector creation.
2599 * Useful for application initialization.
2600 * @description
2601 * Use this method to register work which should be performed when the injector is done
2602 * loading all modules.
2603 */
2604 run: function(block) {
2605 runBlocks.push(block);
2606 return this;
2607 }
2608 };
2609
2610 if (configFn) {
2611 config(configFn);
2612 }
2613
2614 return moduleInstance;
2615
2616 /**
2617 * @param {string} provider
2618 * @param {string} method
2619 * @param {String=} insertMethod
2620 * @returns {angular.Module}
2621 */
2622 function invokeLater(provider, method, insertMethod, queue) {
2623 if (!queue) queue = invokeQueue;
2624 return function() {
2625 queue[insertMethod || 'push']([provider, method, arguments]);
2626 return moduleInstance;
2627 };
2628 }
2629
2630 /**
2631 * @param {string} provider
2632 * @param {string} method
2633 * @returns {angular.Module}
2634 */
2635 function invokeLaterAndSetModuleName(provider, method, queue) {
2636 if (!queue) queue = invokeQueue;
2637 return function(recipeName, factoryFunction) {
2638 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2639 queue.push([provider, method, arguments]);
2640 return moduleInstance;
2641 };
2642 }
2643 });
2644 };
2645 });
2646
2647}
2648
2649/* global shallowCopy: true */
2650
2651/**
2652 * Creates a shallow copy of an object, an array or a primitive.
2653 *
2654 * Assumes that there are no proto properties for objects.
2655 */
2656function shallowCopy(src, dst) {
2657 if (isArray(src)) {
2658 dst = dst || [];
2659
2660 for (var i = 0, ii = src.length; i < ii; i++) {
2661 dst[i] = src[i];
2662 }
2663 } else if (isObject(src)) {
2664 dst = dst || {};
2665
2666 for (var key in src) {
2667 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
2668 dst[key] = src[key];
2669 }
2670 }
2671 }
2672
2673 return dst || src;
2674}
2675
2676/* exported toDebugString */
2677
2678function serializeObject(obj, maxDepth) {
2679 var seen = [];
2680
2681 // There is no direct way to stringify object until reaching a specific depth
2682 // and a very deep object can cause a performance issue, so we copy the object
2683 // based on this specific depth and then stringify it.
2684 if (isValidObjectMaxDepth(maxDepth)) {
2685 // This file is also included in `angular-loader`, so `copy()` might not always be available in
2686 // the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed.
2687 obj = angular.copy(obj, null, maxDepth);
2688 }
2689 return JSON.stringify(obj, function(key, val) {
2690 val = toJsonReplacer(key, val);
2691 if (isObject(val)) {
2692
2693 if (seen.indexOf(val) >= 0) return '...';
2694
2695 seen.push(val);
2696 }
2697 return val;
2698 });
2699}
2700
2701function toDebugString(obj, maxDepth) {
2702 if (typeof obj === 'function') {
2703 return obj.toString().replace(/ \{[\s\S]*$/, '');
2704 } else if (isUndefined(obj)) {
2705 return 'undefined';
2706 } else if (typeof obj !== 'string') {
2707 return serializeObject(obj, maxDepth);
2708 }
2709 return obj;
2710}
2711
2712/* global angularModule: true,
2713 version: true,
2714
2715 $CompileProvider,
2716
2717 htmlAnchorDirective,
2718 inputDirective,
2719 hiddenInputBrowserCacheDirective,
2720 formDirective,
2721 scriptDirective,
2722 selectDirective,
2723 optionDirective,
2724 ngBindDirective,
2725 ngBindHtmlDirective,
2726 ngBindTemplateDirective,
2727 ngClassDirective,
2728 ngClassEvenDirective,
2729 ngClassOddDirective,
2730 ngCloakDirective,
2731 ngControllerDirective,
2732 ngFormDirective,
2733 ngHideDirective,
2734 ngIfDirective,
2735 ngIncludeDirective,
2736 ngIncludeFillContentDirective,
2737 ngInitDirective,
2738 ngNonBindableDirective,
2739 ngPluralizeDirective,
2740 ngRefDirective,
2741 ngRepeatDirective,
2742 ngShowDirective,
2743 ngStyleDirective,
2744 ngSwitchDirective,
2745 ngSwitchWhenDirective,
2746 ngSwitchDefaultDirective,
2747 ngOptionsDirective,
2748 ngTranscludeDirective,
2749 ngModelDirective,
2750 ngListDirective,
2751 ngChangeDirective,
2752 patternDirective,
2753 patternDirective,
2754 requiredDirective,
2755 requiredDirective,
2756 minlengthDirective,
2757 minlengthDirective,
2758 maxlengthDirective,
2759 maxlengthDirective,
2760 ngValueDirective,
2761 ngModelOptionsDirective,
2762 ngAttributeAliasDirectives,
2763 ngEventDirectives,
2764
2765 $AnchorScrollProvider,
2766 $AnimateProvider,
2767 $CoreAnimateCssProvider,
2768 $$CoreAnimateJsProvider,
2769 $$CoreAnimateQueueProvider,
2770 $$AnimateRunnerFactoryProvider,
2771 $$AnimateAsyncRunFactoryProvider,
2772 $BrowserProvider,
2773 $CacheFactoryProvider,
2774 $ControllerProvider,
2775 $DateProvider,
2776 $DocumentProvider,
2777 $$IsDocumentHiddenProvider,
2778 $ExceptionHandlerProvider,
2779 $FilterProvider,
2780 $$ForceReflowProvider,
2781 $InterpolateProvider,
2782 $$IntervalFactoryProvider,
2783 $IntervalProvider,
2784 $HttpProvider,
2785 $HttpParamSerializerProvider,
2786 $HttpParamSerializerJQLikeProvider,
2787 $HttpBackendProvider,
2788 $xhrFactoryProvider,
2789 $jsonpCallbacksProvider,
2790 $LocationProvider,
2791 $LogProvider,
2792 $$MapProvider,
2793 $ParseProvider,
2794 $RootScopeProvider,
2795 $QProvider,
2796 $$QProvider,
2797 $$SanitizeUriProvider,
2798 $SceProvider,
2799 $SceDelegateProvider,
2800 $SnifferProvider,
2801 $$TaskTrackerFactoryProvider,
2802 $TemplateCacheProvider,
2803 $TemplateRequestProvider,
2804 $$TestabilityProvider,
2805 $TimeoutProvider,
2806 $$RAFProvider,
2807 $WindowProvider,
2808 $$jqLiteProvider,
2809 $$CookieReaderProvider
2810*/
2811
2812
2813/**
2814 * @ngdoc object
2815 * @name angular.version
2816 * @module ng
2817 * @description
2818 * An object that contains information about the current AngularJS version.
2819 *
2820 * This object has the following properties:
2821 *
2822 * - `full` – `{string}` – Full version string, such as "0.9.18".
2823 * - `major` – `{number}` – Major version number, such as "0".
2824 * - `minor` – `{number}` – Minor version number, such as "9".
2825 * - `dot` – `{number}` – Dot version number, such as "18".
2826 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2827 */
2828var version = {
2829 // These placeholder strings will be replaced by grunt's `build` task.
2830 // They need to be double- or single-quoted.
2831 full: '1.8.3',
2832 major: 1,
2833 minor: 8,
2834 dot: 3,
2835 codeName: 'ultimate-farewell'
2836};
2837
2838
2839function publishExternalAPI(angular) {
2840 extend(angular, {
2841 'errorHandlingConfig': errorHandlingConfig,
2842 'bootstrap': bootstrap,
2843 'copy': copy,
2844 'extend': extend,
2845 'merge': merge,
2846 'equals': equals,
2847 'element': jqLite,
2848 'forEach': forEach,
2849 'injector': createInjector,
2850 'noop': noop,
2851 'bind': bind,
2852 'toJson': toJson,
2853 'fromJson': fromJson,
2854 'identity': identity,
2855 'isUndefined': isUndefined,
2856 'isDefined': isDefined,
2857 'isString': isString,
2858 'isFunction': isFunction,
2859 'isObject': isObject,
2860 'isNumber': isNumber,
2861 'isElement': isElement,
2862 'isArray': isArray,
2863 'version': version,
2864 'isDate': isDate,
2865 'callbacks': {$$counter: 0},
2866 'getTestability': getTestability,
2867 'reloadWithDebugInfo': reloadWithDebugInfo,
2868 'UNSAFE_restoreLegacyJqLiteXHTMLReplacement': UNSAFE_restoreLegacyJqLiteXHTMLReplacement,
2869 '$$minErr': minErr,
2870 '$$csp': csp,
2871 '$$encodeUriSegment': encodeUriSegment,
2872 '$$encodeUriQuery': encodeUriQuery,
2873 '$$lowercase': lowercase,
2874 '$$stringify': stringify,
2875 '$$uppercase': uppercase
2876 });
2877
2878 angularModule = setupModuleLoader(window);
2879
2880 angularModule('ng', ['ngLocale'], ['$provide',
2881 function ngModule($provide) {
2882 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2883 $provide.provider({
2884 $$sanitizeUri: $$SanitizeUriProvider
2885 });
2886 $provide.provider('$compile', $CompileProvider).
2887 directive({
2888 a: htmlAnchorDirective,
2889 input: inputDirective,
2890 textarea: inputDirective,
2891 form: formDirective,
2892 script: scriptDirective,
2893 select: selectDirective,
2894 option: optionDirective,
2895 ngBind: ngBindDirective,
2896 ngBindHtml: ngBindHtmlDirective,
2897 ngBindTemplate: ngBindTemplateDirective,
2898 ngClass: ngClassDirective,
2899 ngClassEven: ngClassEvenDirective,
2900 ngClassOdd: ngClassOddDirective,
2901 ngCloak: ngCloakDirective,
2902 ngController: ngControllerDirective,
2903 ngForm: ngFormDirective,
2904 ngHide: ngHideDirective,
2905 ngIf: ngIfDirective,
2906 ngInclude: ngIncludeDirective,
2907 ngInit: ngInitDirective,
2908 ngNonBindable: ngNonBindableDirective,
2909 ngPluralize: ngPluralizeDirective,
2910 ngRef: ngRefDirective,
2911 ngRepeat: ngRepeatDirective,
2912 ngShow: ngShowDirective,
2913 ngStyle: ngStyleDirective,
2914 ngSwitch: ngSwitchDirective,
2915 ngSwitchWhen: ngSwitchWhenDirective,
2916 ngSwitchDefault: ngSwitchDefaultDirective,
2917 ngOptions: ngOptionsDirective,
2918 ngTransclude: ngTranscludeDirective,
2919 ngModel: ngModelDirective,
2920 ngList: ngListDirective,
2921 ngChange: ngChangeDirective,
2922 pattern: patternDirective,
2923 ngPattern: patternDirective,
2924 required: requiredDirective,
2925 ngRequired: requiredDirective,
2926 minlength: minlengthDirective,
2927 ngMinlength: minlengthDirective,
2928 maxlength: maxlengthDirective,
2929 ngMaxlength: maxlengthDirective,
2930 ngValue: ngValueDirective,
2931 ngModelOptions: ngModelOptionsDirective
2932 }).
2933 directive({
2934 ngInclude: ngIncludeFillContentDirective,
2935 input: hiddenInputBrowserCacheDirective
2936 }).
2937 directive(ngAttributeAliasDirectives).
2938 directive(ngEventDirectives);
2939 $provide.provider({
2940 $anchorScroll: $AnchorScrollProvider,
2941 $animate: $AnimateProvider,
2942 $animateCss: $CoreAnimateCssProvider,
2943 $$animateJs: $$CoreAnimateJsProvider,
2944 $$animateQueue: $$CoreAnimateQueueProvider,
2945 $$AnimateRunner: $$AnimateRunnerFactoryProvider,
2946 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
2947 $browser: $BrowserProvider,
2948 $cacheFactory: $CacheFactoryProvider,
2949 $controller: $ControllerProvider,
2950 $document: $DocumentProvider,
2951 $$isDocumentHidden: $$IsDocumentHiddenProvider,
2952 $exceptionHandler: $ExceptionHandlerProvider,
2953 $filter: $FilterProvider,
2954 $$forceReflow: $$ForceReflowProvider,
2955 $interpolate: $InterpolateProvider,
2956 $interval: $IntervalProvider,
2957 $$intervalFactory: $$IntervalFactoryProvider,
2958 $http: $HttpProvider,
2959 $httpParamSerializer: $HttpParamSerializerProvider,
2960 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2961 $httpBackend: $HttpBackendProvider,
2962 $xhrFactory: $xhrFactoryProvider,
2963 $jsonpCallbacks: $jsonpCallbacksProvider,
2964 $location: $LocationProvider,
2965 $log: $LogProvider,
2966 $parse: $ParseProvider,
2967 $rootScope: $RootScopeProvider,
2968 $q: $QProvider,
2969 $$q: $$QProvider,
2970 $sce: $SceProvider,
2971 $sceDelegate: $SceDelegateProvider,
2972 $sniffer: $SnifferProvider,
2973 $$taskTrackerFactory: $$TaskTrackerFactoryProvider,
2974 $templateCache: $TemplateCacheProvider,
2975 $templateRequest: $TemplateRequestProvider,
2976 $$testability: $$TestabilityProvider,
2977 $timeout: $TimeoutProvider,
2978 $window: $WindowProvider,
2979 $$rAF: $$RAFProvider,
2980 $$jqLite: $$jqLiteProvider,
2981 $$Map: $$MapProvider,
2982 $$cookieReader: $$CookieReaderProvider
2983 });
2984 }
2985 ])
2986 .info({ angularVersion: '1.8.3' });
2987}
2988
2989/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2990 * Any commits to this file should be reviewed with security in mind. *
2991 * Changes to this file can potentially create security vulnerabilities. *
2992 * An approval from 2 Core members with history of modifying *
2993 * this file is required. *
2994 * *
2995 * Does the change somehow allow for arbitrary javascript to be executed? *
2996 * Or allows for someone to change the prototype of built-in objects? *
2997 * Or gives undesired access to variables likes document or window? *
2998 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2999
3000/* global
3001 JQLitePrototype: true,
3002 BOOLEAN_ATTR: true,
3003 ALIASED_ATTR: true
3004*/
3005
3006//////////////////////////////////
3007//JQLite
3008//////////////////////////////////
3009
3010/**
3011 * @ngdoc function
3012 * @name angular.element
3013 * @module ng
3014 * @kind function
3015 *
3016 * @description
3017 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
3018 *
3019 * If jQuery is available, `angular.element` is an alias for the
3020 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
3021 * delegates to AngularJS's built-in subset of jQuery, called "jQuery lite" or **jqLite**.
3022 *
3023 * jqLite is a tiny, API-compatible subset of jQuery that allows
3024 * AngularJS to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most
3025 * commonly needed functionality with the goal of having a very small footprint.
3026 *
3027 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the
3028 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a
3029 * specific version of jQuery if multiple versions exist on the page.
3030 *
3031 * <div class="alert alert-info">**Note:** All element references in AngularJS are always wrapped with jQuery or
3032 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div>
3033 *
3034 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements
3035 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)`
3036 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div>
3037 *
3038 * ## AngularJS's jqLite
3039 * jqLite provides only the following jQuery methods:
3040 *
3041 * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
3042 * - [`after()`](http://api.jquery.com/after/)
3043 * - [`append()`](http://api.jquery.com/append/) - Contrary to jQuery, this doesn't clone elements
3044 * so will not work correctly when invoked on a jqLite object containing more than one DOM node
3045 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
3046 * - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData
3047 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
3048 * - [`clone()`](http://api.jquery.com/clone/)
3049 * - [`contents()`](http://api.jquery.com/contents/)
3050 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
3051 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
3052 * - [`data()`](http://api.jquery.com/data/)
3053 * - [`detach()`](http://api.jquery.com/detach/)
3054 * - [`empty()`](http://api.jquery.com/empty/)
3055 * - [`eq()`](http://api.jquery.com/eq/)
3056 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
3057 * - [`hasClass()`](http://api.jquery.com/hasClass/)
3058 * - [`html()`](http://api.jquery.com/html/)
3059 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
3060 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
3061 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
3062 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
3063 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
3064 * - [`prepend()`](http://api.jquery.com/prepend/)
3065 * - [`prop()`](http://api.jquery.com/prop/)
3066 * - [`ready()`](http://api.jquery.com/ready/) (_deprecated_, use `angular.element(callback)` instead of `angular.element(document).ready(callback)`)
3067 * - [`remove()`](http://api.jquery.com/remove/)
3068 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes
3069 * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument
3070 * - [`removeData()`](http://api.jquery.com/removeData/)
3071 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
3072 * - [`text()`](http://api.jquery.com/text/)
3073 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument
3074 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers
3075 * - [`unbind()`](http://api.jquery.com/unbind/) (_deprecated_, use [`off()`](http://api.jquery.com/off/)) - Does not support namespaces or event object as parameter
3076 * - [`val()`](http://api.jquery.com/val/)
3077 * - [`wrap()`](http://api.jquery.com/wrap/)
3078 *
3079 * jqLite also provides a method restoring pre-1.8 insecure treatment of XHTML-like tags.
3080 * This legacy behavior turns input like `<div /><span />` to `<div></div><span></span>`
3081 * instead of `<div><span></span></div>` like version 1.8 & newer do. To restore it, invoke:
3082 * ```js
3083 * angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement();
3084 * ```
3085 * Note that this only patches jqLite. If you use jQuery 3.5.0 or newer, please read the
3086 * [jQuery 3.5 upgrade guide](https://jquery.com/upgrade-guide/3.5/) for more details
3087 * about the workarounds.
3088 *
3089 * ## jQuery/jqLite Extras
3090 * AngularJS also provides the following additional methods and events to both jQuery and jqLite:
3091 *
3092 * ### Events
3093 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
3094 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
3095 * element before it is removed.
3096 *
3097 * ### Methods
3098 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
3099 * retrieves controller associated with the `ngController` directive. If `name` is provided as
3100 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
3101 * `'ngModel'`).
3102 * - `injector()` - retrieves the injector of the current element or its parent.
3103 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
3104 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
3105 * be enabled.
3106 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
3107 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
3108 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
3109 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
3110 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
3111 * parent element is reached.
3112 *
3113 * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See
3114 * https://github.com/angular/angular.js/issues/14251 for more information.
3115 *
3116 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
3117 * @returns {Object} jQuery object.
3118 */
3119
3120JQLite.expando = 'ng339';
3121
3122var jqCache = JQLite.cache = {},
3123 jqId = 1;
3124
3125/*
3126 * !!! This is an undocumented "private" function !!!
3127 */
3128JQLite._data = function(node) {
3129 //jQuery always returns an object on cache miss
3130 return this.cache[node[this.expando]] || {};
3131};
3132
3133function jqNextId() { return ++jqId; }
3134
3135
3136var DASH_LOWERCASE_REGEXP = /-([a-z])/g;
3137var MS_HACK_REGEXP = /^-ms-/;
3138var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' };
3139var jqLiteMinErr = minErr('jqLite');
3140
3141/**
3142 * Converts kebab-case to camelCase.
3143 * There is also a special case for the ms prefix starting with a lowercase letter.
3144 * @param name Name to normalize
3145 */
3146function cssKebabToCamel(name) {
3147 return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-'));
3148}
3149
3150function fnCamelCaseReplace(all, letter) {
3151 return letter.toUpperCase();
3152}
3153
3154/**
3155 * Converts kebab-case to camelCase.
3156 * @param name Name to normalize
3157 */
3158function kebabToCamel(name) {
3159 return name
3160 .replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
3161}
3162
3163var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
3164var HTML_REGEXP = /<|&#?\w+;/;
3165var TAG_NAME_REGEXP = /<([\w:-]+)/;
3166var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
3167
3168// Table parts need to be wrapped with `<table>` or they're
3169// stripped to their contents when put in a div.
3170// XHTML parsers do not magically insert elements in the
3171// same way that tag soup parsers do, so we cannot shorten
3172// this by omitting <tbody> or other required elements.
3173var wrapMap = {
3174 thead: ['table'],
3175 col: ['colgroup', 'table'],
3176 tr: ['tbody', 'table'],
3177 td: ['tr', 'tbody', 'table']
3178};
3179
3180wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
3181wrapMap.th = wrapMap.td;
3182
3183// Support: IE <10 only
3184// IE 9 requires an option wrapper & it needs to have the whole table structure
3185// set up in advance; assigning `"<td></td>"` to `tr.innerHTML` doesn't work, etc.
3186var wrapMapIE9 = {
3187 option: [1, '<select multiple="multiple">', '</select>'],
3188 _default: [0, '', '']
3189};
3190
3191for (var key in wrapMap) {
3192 var wrapMapValueClosing = wrapMap[key];
3193 var wrapMapValue = wrapMapValueClosing.slice().reverse();
3194 wrapMapIE9[key] = [wrapMapValue.length, '<' + wrapMapValue.join('><') + '>', '</' + wrapMapValueClosing.join('></') + '>'];
3195}
3196
3197wrapMapIE9.optgroup = wrapMapIE9.option;
3198
3199function jqLiteIsTextNode(html) {
3200 return !HTML_REGEXP.test(html);
3201}
3202
3203function jqLiteAcceptsData(node) {
3204 // The window object can accept data but has no nodeType
3205 // Otherwise we are only interested in elements (1) and documents (9)
3206 var nodeType = node.nodeType;
3207 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
3208}
3209
3210function jqLiteHasData(node) {
3211 for (var key in jqCache[node.ng339]) {
3212 return true;
3213 }
3214 return false;
3215}
3216
3217function jqLiteBuildFragment(html, context) {
3218 var tmp, tag, wrap, finalHtml,
3219 fragment = context.createDocumentFragment(),
3220 nodes = [], i;
3221
3222 if (jqLiteIsTextNode(html)) {
3223 // Convert non-html into a text node
3224 nodes.push(context.createTextNode(html));
3225 } else {
3226 // Convert html into DOM nodes
3227 tmp = fragment.appendChild(context.createElement('div'));
3228 tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase();
3229 finalHtml = JQLite.legacyXHTMLReplacement ?
3230 html.replace(XHTML_TAG_REGEXP, '<$1></$2>') :
3231 html;
3232
3233 if (msie < 10) {
3234 wrap = wrapMapIE9[tag] || wrapMapIE9._default;
3235 tmp.innerHTML = wrap[1] + finalHtml + wrap[2];
3236
3237 // Descend through wrappers to the right content
3238 i = wrap[0];
3239 while (i--) {
3240 tmp = tmp.firstChild;
3241 }
3242 } else {
3243 wrap = wrapMap[tag] || [];
3244
3245 // Create wrappers & descend into them
3246 i = wrap.length;
3247 while (--i > -1) {
3248 tmp.appendChild(window.document.createElement(wrap[i]));
3249 tmp = tmp.firstChild;
3250 }
3251
3252 tmp.innerHTML = finalHtml;
3253 }
3254
3255 nodes = concat(nodes, tmp.childNodes);
3256
3257 tmp = fragment.firstChild;
3258 tmp.textContent = '';
3259 }
3260
3261 // Remove wrapper from fragment
3262 fragment.textContent = '';
3263 fragment.innerHTML = ''; // Clear inner HTML
3264 forEach(nodes, function(node) {
3265 fragment.appendChild(node);
3266 });
3267
3268 return fragment;
3269}
3270
3271function jqLiteParseHTML(html, context) {
3272 context = context || window.document;
3273 var parsed;
3274
3275 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
3276 return [context.createElement(parsed[1])];
3277 }
3278
3279 if ((parsed = jqLiteBuildFragment(html, context))) {
3280 return parsed.childNodes;
3281 }
3282
3283 return [];
3284}
3285
3286function jqLiteWrapNode(node, wrapper) {
3287 var parent = node.parentNode;
3288
3289 if (parent) {
3290 parent.replaceChild(wrapper, node);
3291 }
3292
3293 wrapper.appendChild(node);
3294}
3295
3296
3297// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
3298var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) {
3299 // eslint-disable-next-line no-bitwise
3300 return !!(this.compareDocumentPosition(arg) & 16);
3301};
3302
3303/////////////////////////////////////////////
3304function JQLite(element) {
3305 if (element instanceof JQLite) {
3306 return element;
3307 }
3308
3309 var argIsString;
3310
3311 if (isString(element)) {
3312 element = trim(element);
3313 argIsString = true;
3314 }
3315 if (!(this instanceof JQLite)) {
3316 if (argIsString && element.charAt(0) !== '<') {
3317 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
3318 }
3319 return new JQLite(element);
3320 }
3321
3322 if (argIsString) {
3323 jqLiteAddNodes(this, jqLiteParseHTML(element));
3324 } else if (isFunction(element)) {
3325 jqLiteReady(element);
3326 } else {
3327 jqLiteAddNodes(this, element);
3328 }
3329}
3330
3331function jqLiteClone(element) {
3332 return element.cloneNode(true);
3333}
3334
3335function jqLiteDealoc(element, onlyDescendants) {
3336 if (!onlyDescendants && jqLiteAcceptsData(element)) jqLite.cleanData([element]);
3337
3338 if (element.querySelectorAll) {
3339 jqLite.cleanData(element.querySelectorAll('*'));
3340 }
3341}
3342
3343function isEmptyObject(obj) {
3344 var name;
3345
3346 for (name in obj) {
3347 return false;
3348 }
3349 return true;
3350}
3351
3352function removeIfEmptyData(element) {
3353 var expandoId = element.ng339;
3354 var expandoStore = expandoId && jqCache[expandoId];
3355
3356 var events = expandoStore && expandoStore.events;
3357 var data = expandoStore && expandoStore.data;
3358
3359 if ((!data || isEmptyObject(data)) && (!events || isEmptyObject(events))) {
3360 delete jqCache[expandoId];
3361 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
3362 }
3363}
3364
3365function jqLiteOff(element, type, fn, unsupported) {
3366 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
3367
3368 var expandoStore = jqLiteExpandoStore(element);
3369 var events = expandoStore && expandoStore.events;
3370 var handle = expandoStore && expandoStore.handle;
3371
3372 if (!handle) return; //no listeners registered
3373
3374 if (!type) {
3375 for (type in events) {
3376 if (type !== '$destroy') {
3377 element.removeEventListener(type, handle);
3378 }
3379 delete events[type];
3380 }
3381 } else {
3382
3383 var removeHandler = function(type) {
3384 var listenerFns = events[type];
3385 if (isDefined(fn)) {
3386 arrayRemove(listenerFns || [], fn);
3387 }
3388 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
3389 element.removeEventListener(type, handle);
3390 delete events[type];
3391 }
3392 };
3393
3394 forEach(type.split(' '), function(type) {
3395 removeHandler(type);
3396 if (MOUSE_EVENT_MAP[type]) {
3397 removeHandler(MOUSE_EVENT_MAP[type]);
3398 }
3399 });
3400 }
3401
3402 removeIfEmptyData(element);
3403}
3404
3405function jqLiteRemoveData(element, name) {
3406 var expandoId = element.ng339;
3407 var expandoStore = expandoId && jqCache[expandoId];
3408
3409 if (expandoStore) {
3410 if (name) {
3411 delete expandoStore.data[name];
3412 } else {
3413 expandoStore.data = {};
3414 }
3415
3416 removeIfEmptyData(element);
3417 }
3418}
3419
3420
3421function jqLiteExpandoStore(element, createIfNecessary) {
3422 var expandoId = element.ng339,
3423 expandoStore = expandoId && jqCache[expandoId];
3424
3425 if (createIfNecessary && !expandoStore) {
3426 element.ng339 = expandoId = jqNextId();
3427 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
3428 }
3429
3430 return expandoStore;
3431}
3432
3433
3434function jqLiteData(element, key, value) {
3435 if (jqLiteAcceptsData(element)) {
3436 var prop;
3437
3438 var isSimpleSetter = isDefined(value);
3439 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
3440 var massGetter = !key;
3441 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
3442 var data = expandoStore && expandoStore.data;
3443
3444 if (isSimpleSetter) { // data('key', value)
3445 data[kebabToCamel(key)] = value;
3446 } else {
3447 if (massGetter) { // data()
3448 return data;
3449 } else {
3450 if (isSimpleGetter) { // data('key')
3451 // don't force creation of expandoStore if it doesn't exist yet
3452 return data && data[kebabToCamel(key)];
3453 } else { // mass-setter: data({key1: val1, key2: val2})
3454 for (prop in key) {
3455 data[kebabToCamel(prop)] = key[prop];
3456 }
3457 }
3458 }
3459 }
3460 }
3461}
3462
3463function jqLiteHasClass(element, selector) {
3464 if (!element.getAttribute) return false;
3465 return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' ').
3466 indexOf(' ' + selector + ' ') > -1);
3467}
3468
3469function jqLiteRemoveClass(element, cssClasses) {
3470 if (cssClasses && element.setAttribute) {
3471 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3472 .replace(/[\n\t]/g, ' ');
3473 var newClasses = existingClasses;
3474
3475 forEach(cssClasses.split(' '), function(cssClass) {
3476 cssClass = trim(cssClass);
3477 newClasses = newClasses.replace(' ' + cssClass + ' ', ' ');
3478 });
3479
3480 if (newClasses !== existingClasses) {
3481 element.setAttribute('class', trim(newClasses));
3482 }
3483 }
3484}
3485
3486function jqLiteAddClass(element, cssClasses) {
3487 if (cssClasses && element.setAttribute) {
3488 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3489 .replace(/[\n\t]/g, ' ');
3490 var newClasses = existingClasses;
3491
3492 forEach(cssClasses.split(' '), function(cssClass) {
3493 cssClass = trim(cssClass);
3494 if (newClasses.indexOf(' ' + cssClass + ' ') === -1) {
3495 newClasses += cssClass + ' ';
3496 }
3497 });
3498
3499 if (newClasses !== existingClasses) {
3500 element.setAttribute('class', trim(newClasses));
3501 }
3502 }
3503}
3504
3505
3506function jqLiteAddNodes(root, elements) {
3507 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3508
3509 if (elements) {
3510
3511 // if a Node (the most common case)
3512 if (elements.nodeType) {
3513 root[root.length++] = elements;
3514 } else {
3515 var length = elements.length;
3516
3517 // if an Array or NodeList and not a Window
3518 if (typeof length === 'number' && elements.window !== elements) {
3519 if (length) {
3520 for (var i = 0; i < length; i++) {
3521 root[root.length++] = elements[i];
3522 }
3523 }
3524 } else {
3525 root[root.length++] = elements;
3526 }
3527 }
3528 }
3529}
3530
3531
3532function jqLiteController(element, name) {
3533 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3534}
3535
3536function jqLiteInheritedData(element, name, value) {
3537 // if element is the document object work with the html element instead
3538 // this makes $(document).scope() possible
3539 if (element.nodeType === NODE_TYPE_DOCUMENT) {
3540 element = element.documentElement;
3541 }
3542 var names = isArray(name) ? name : [name];
3543
3544 while (element) {
3545 for (var i = 0, ii = names.length; i < ii; i++) {
3546 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3547 }
3548
3549 // If dealing with a document fragment node with a host element, and no parent, use the host
3550 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3551 // to lookup parent controllers.
3552 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3553 }
3554}
3555
3556function jqLiteEmpty(element) {
3557 jqLiteDealoc(element, true);
3558 while (element.firstChild) {
3559 element.removeChild(element.firstChild);
3560 }
3561}
3562
3563function jqLiteRemove(element, keepData) {
3564 if (!keepData) jqLiteDealoc(element);
3565 var parent = element.parentNode;
3566 if (parent) parent.removeChild(element);
3567}
3568
3569
3570function jqLiteDocumentLoaded(action, win) {
3571 win = win || window;
3572 if (win.document.readyState === 'complete') {
3573 // Force the action to be run async for consistent behavior
3574 // from the action's point of view
3575 // i.e. it will definitely not be in a $apply
3576 win.setTimeout(action);
3577 } else {
3578 // No need to unbind this handler as load is only ever called once
3579 jqLite(win).on('load', action);
3580 }
3581}
3582
3583function jqLiteReady(fn) {
3584 function trigger() {
3585 window.document.removeEventListener('DOMContentLoaded', trigger);
3586 window.removeEventListener('load', trigger);
3587 fn();
3588 }
3589
3590 // check if document is already loaded
3591 if (window.document.readyState === 'complete') {
3592 window.setTimeout(fn);
3593 } else {
3594 // We can not use jqLite since we are not done loading and jQuery could be loaded later.
3595
3596 // Works for modern browsers and IE9
3597 window.document.addEventListener('DOMContentLoaded', trigger);
3598
3599 // Fallback to window.onload for others
3600 window.addEventListener('load', trigger);
3601 }
3602}
3603
3604//////////////////////////////////////////
3605// Functions which are declared directly.
3606//////////////////////////////////////////
3607var JQLitePrototype = JQLite.prototype = {
3608 ready: jqLiteReady,
3609 toString: function() {
3610 var value = [];
3611 forEach(this, function(e) { value.push('' + e);});
3612 return '[' + value.join(', ') + ']';
3613 },
3614
3615 eq: function(index) {
3616 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3617 },
3618
3619 length: 0,
3620 push: push,
3621 sort: [].sort,
3622 splice: [].splice
3623};
3624
3625//////////////////////////////////////////
3626// Functions iterating getter/setters.
3627// these functions return self on setter and
3628// value on get.
3629//////////////////////////////////////////
3630var BOOLEAN_ATTR = {};
3631forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3632 BOOLEAN_ATTR[lowercase(value)] = value;
3633});
3634var BOOLEAN_ELEMENTS = {};
3635forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3636 BOOLEAN_ELEMENTS[value] = true;
3637});
3638var ALIASED_ATTR = {
3639 'ngMinlength': 'minlength',
3640 'ngMaxlength': 'maxlength',
3641 'ngMin': 'min',
3642 'ngMax': 'max',
3643 'ngPattern': 'pattern',
3644 'ngStep': 'step'
3645};
3646
3647function getBooleanAttrName(element, name) {
3648 // check dom last since we will most likely fail on name
3649 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3650
3651 // booleanAttr is here twice to minimize DOM access
3652 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3653}
3654
3655function getAliasedAttrName(name) {
3656 return ALIASED_ATTR[name];
3657}
3658
3659forEach({
3660 data: jqLiteData,
3661 removeData: jqLiteRemoveData,
3662 hasData: jqLiteHasData,
3663 cleanData: function jqLiteCleanData(nodes) {
3664 for (var i = 0, ii = nodes.length; i < ii; i++) {
3665 jqLiteRemoveData(nodes[i]);
3666 jqLiteOff(nodes[i]);
3667 }
3668 }
3669}, function(fn, name) {
3670 JQLite[name] = fn;
3671});
3672
3673forEach({
3674 data: jqLiteData,
3675 inheritedData: jqLiteInheritedData,
3676
3677 scope: function(element) {
3678 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3679 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3680 },
3681
3682 isolateScope: function(element) {
3683 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3684 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3685 },
3686
3687 controller: jqLiteController,
3688
3689 injector: function(element) {
3690 return jqLiteInheritedData(element, '$injector');
3691 },
3692
3693 removeAttr: function(element, name) {
3694 element.removeAttribute(name);
3695 },
3696
3697 hasClass: jqLiteHasClass,
3698
3699 css: function(element, name, value) {
3700 name = cssKebabToCamel(name);
3701
3702 if (isDefined(value)) {
3703 element.style[name] = value;
3704 } else {
3705 return element.style[name];
3706 }
3707 },
3708
3709 attr: function(element, name, value) {
3710 var ret;
3711 var nodeType = element.nodeType;
3712 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT ||
3713 !element.getAttribute) {
3714 return;
3715 }
3716
3717 var lowercasedName = lowercase(name);
3718 var isBooleanAttr = BOOLEAN_ATTR[lowercasedName];
3719
3720 if (isDefined(value)) {
3721 // setter
3722
3723 if (value === null || (value === false && isBooleanAttr)) {
3724 element.removeAttribute(name);
3725 } else {
3726 element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
3727 }
3728 } else {
3729 // getter
3730
3731 ret = element.getAttribute(name);
3732
3733 if (isBooleanAttr && ret !== null) {
3734 ret = lowercasedName;
3735 }
3736 // Normalize non-existing attributes to undefined (as jQuery).
3737 return ret === null ? undefined : ret;
3738 }
3739 },
3740
3741 prop: function(element, name, value) {
3742 if (isDefined(value)) {
3743 element[name] = value;
3744 } else {
3745 return element[name];
3746 }
3747 },
3748
3749 text: (function() {
3750 getText.$dv = '';
3751 return getText;
3752
3753 function getText(element, value) {
3754 if (isUndefined(value)) {
3755 var nodeType = element.nodeType;
3756 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3757 }
3758 element.textContent = value;
3759 }
3760 })(),
3761
3762 val: function(element, value) {
3763 if (isUndefined(value)) {
3764 if (element.multiple && nodeName_(element) === 'select') {
3765 var result = [];
3766 forEach(element.options, function(option) {
3767 if (option.selected) {
3768 result.push(option.value || option.text);
3769 }
3770 });
3771 return result;
3772 }
3773 return element.value;
3774 }
3775 element.value = value;
3776 },
3777
3778 html: function(element, value) {
3779 if (isUndefined(value)) {
3780 return element.innerHTML;
3781 }
3782 jqLiteDealoc(element, true);
3783 element.innerHTML = value;
3784 },
3785
3786 empty: jqLiteEmpty
3787}, function(fn, name) {
3788 /**
3789 * Properties: writes return selection, reads return first value
3790 */
3791 JQLite.prototype[name] = function(arg1, arg2) {
3792 var i, key;
3793 var nodeCount = this.length;
3794
3795 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3796 // in a way that survives minification.
3797 // jqLiteEmpty takes no arguments but is a setter.
3798 if (fn !== jqLiteEmpty &&
3799 (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3800 if (isObject(arg1)) {
3801
3802 // we are a write, but the object properties are the key/values
3803 for (i = 0; i < nodeCount; i++) {
3804 if (fn === jqLiteData) {
3805 // data() takes the whole object in jQuery
3806 fn(this[i], arg1);
3807 } else {
3808 for (key in arg1) {
3809 fn(this[i], key, arg1[key]);
3810 }
3811 }
3812 }
3813 // return self for chaining
3814 return this;
3815 } else {
3816 // we are a read, so read the first child.
3817 // TODO: do we still need this?
3818 var value = fn.$dv;
3819 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3820 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3821 for (var j = 0; j < jj; j++) {
3822 var nodeValue = fn(this[j], arg1, arg2);
3823 value = value ? value + nodeValue : nodeValue;
3824 }
3825 return value;
3826 }
3827 } else {
3828 // we are a write, so apply to all children
3829 for (i = 0; i < nodeCount; i++) {
3830 fn(this[i], arg1, arg2);
3831 }
3832 // return self for chaining
3833 return this;
3834 }
3835 };
3836});
3837
3838function createEventHandler(element, events) {
3839 var eventHandler = function(event, type) {
3840 // jQuery specific api
3841 event.isDefaultPrevented = function() {
3842 return event.defaultPrevented;
3843 };
3844
3845 var eventFns = events[type || event.type];
3846 var eventFnsLength = eventFns ? eventFns.length : 0;
3847
3848 if (!eventFnsLength) return;
3849
3850 if (isUndefined(event.immediatePropagationStopped)) {
3851 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3852 event.stopImmediatePropagation = function() {
3853 event.immediatePropagationStopped = true;
3854
3855 if (event.stopPropagation) {
3856 event.stopPropagation();
3857 }
3858
3859 if (originalStopImmediatePropagation) {
3860 originalStopImmediatePropagation.call(event);
3861 }
3862 };
3863 }
3864
3865 event.isImmediatePropagationStopped = function() {
3866 return event.immediatePropagationStopped === true;
3867 };
3868
3869 // Some events have special handlers that wrap the real handler
3870 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3871
3872 // Copy event handlers in case event handlers array is modified during execution.
3873 if ((eventFnsLength > 1)) {
3874 eventFns = shallowCopy(eventFns);
3875 }
3876
3877 for (var i = 0; i < eventFnsLength; i++) {
3878 if (!event.isImmediatePropagationStopped()) {
3879 handlerWrapper(element, event, eventFns[i]);
3880 }
3881 }
3882 };
3883
3884 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3885 // events on `element`
3886 eventHandler.elem = element;
3887 return eventHandler;
3888}
3889
3890function defaultHandlerWrapper(element, event, handler) {
3891 handler.call(element, event);
3892}
3893
3894function specialMouseHandlerWrapper(target, event, handler) {
3895 // Refer to jQuery's implementation of mouseenter & mouseleave
3896 // Read about mouseenter and mouseleave:
3897 // http://www.quirksmode.org/js/events_mouse.html#link8
3898 var related = event.relatedTarget;
3899 // For mousenter/leave call the handler if related is outside the target.
3900 // NB: No relatedTarget if the mouse left/entered the browser window
3901 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3902 handler.call(target, event);
3903 }
3904}
3905
3906//////////////////////////////////////////
3907// Functions iterating traversal.
3908// These functions chain results into a single
3909// selector.
3910//////////////////////////////////////////
3911forEach({
3912 removeData: jqLiteRemoveData,
3913
3914 on: function jqLiteOn(element, type, fn, unsupported) {
3915 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3916
3917 // Do not add event handlers to non-elements because they will not be cleaned up.
3918 if (!jqLiteAcceptsData(element)) {
3919 return;
3920 }
3921
3922 var expandoStore = jqLiteExpandoStore(element, true);
3923 var events = expandoStore.events;
3924 var handle = expandoStore.handle;
3925
3926 if (!handle) {
3927 handle = expandoStore.handle = createEventHandler(element, events);
3928 }
3929
3930 // http://jsperf.com/string-indexof-vs-split
3931 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3932 var i = types.length;
3933
3934 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3935 var eventFns = events[type];
3936
3937 if (!eventFns) {
3938 eventFns = events[type] = [];
3939 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3940 if (type !== '$destroy' && !noEventListener) {
3941 element.addEventListener(type, handle);
3942 }
3943 }
3944
3945 eventFns.push(fn);
3946 };
3947
3948 while (i--) {
3949 type = types[i];
3950 if (MOUSE_EVENT_MAP[type]) {
3951 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3952 addHandler(type, undefined, true);
3953 } else {
3954 addHandler(type);
3955 }
3956 }
3957 },
3958
3959 off: jqLiteOff,
3960
3961 one: function(element, type, fn) {
3962 element = jqLite(element);
3963
3964 //add the listener twice so that when it is called
3965 //you can remove the original function and still be
3966 //able to call element.off(ev, fn) normally
3967 element.on(type, function onFn() {
3968 element.off(type, fn);
3969 element.off(type, onFn);
3970 });
3971 element.on(type, fn);
3972 },
3973
3974 replaceWith: function(element, replaceNode) {
3975 var index, parent = element.parentNode;
3976 jqLiteDealoc(element);
3977 forEach(new JQLite(replaceNode), function(node) {
3978 if (index) {
3979 parent.insertBefore(node, index.nextSibling);
3980 } else {
3981 parent.replaceChild(node, element);
3982 }
3983 index = node;
3984 });
3985 },
3986
3987 children: function(element) {
3988 var children = [];
3989 forEach(element.childNodes, function(element) {
3990 if (element.nodeType === NODE_TYPE_ELEMENT) {
3991 children.push(element);
3992 }
3993 });
3994 return children;
3995 },
3996
3997 contents: function(element) {
3998 return element.contentDocument || element.childNodes || [];
3999 },
4000
4001 append: function(element, node) {
4002 var nodeType = element.nodeType;
4003 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
4004
4005 node = new JQLite(node);
4006
4007 for (var i = 0, ii = node.length; i < ii; i++) {
4008 var child = node[i];
4009 element.appendChild(child);
4010 }
4011 },
4012
4013 prepend: function(element, node) {
4014 if (element.nodeType === NODE_TYPE_ELEMENT) {
4015 var index = element.firstChild;
4016 forEach(new JQLite(node), function(child) {
4017 element.insertBefore(child, index);
4018 });
4019 }
4020 },
4021
4022 wrap: function(element, wrapNode) {
4023 jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]);
4024 },
4025
4026 remove: jqLiteRemove,
4027
4028 detach: function(element) {
4029 jqLiteRemove(element, true);
4030 },
4031
4032 after: function(element, newElement) {
4033 var index = element, parent = element.parentNode;
4034
4035 if (parent) {
4036 newElement = new JQLite(newElement);
4037
4038 for (var i = 0, ii = newElement.length; i < ii; i++) {
4039 var node = newElement[i];
4040 parent.insertBefore(node, index.nextSibling);
4041 index = node;
4042 }
4043 }
4044 },
4045
4046 addClass: jqLiteAddClass,
4047 removeClass: jqLiteRemoveClass,
4048
4049 toggleClass: function(element, selector, condition) {
4050 if (selector) {
4051 forEach(selector.split(' '), function(className) {
4052 var classCondition = condition;
4053 if (isUndefined(classCondition)) {
4054 classCondition = !jqLiteHasClass(element, className);
4055 }
4056 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
4057 });
4058 }
4059 },
4060
4061 parent: function(element) {
4062 var parent = element.parentNode;
4063 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
4064 },
4065
4066 next: function(element) {
4067 return element.nextElementSibling;
4068 },
4069
4070 find: function(element, selector) {
4071 if (element.getElementsByTagName) {
4072 return element.getElementsByTagName(selector);
4073 } else {
4074 return [];
4075 }
4076 },
4077
4078 clone: jqLiteClone,
4079
4080 triggerHandler: function(element, event, extraParameters) {
4081
4082 var dummyEvent, eventFnsCopy, handlerArgs;
4083 var eventName = event.type || event;
4084 var expandoStore = jqLiteExpandoStore(element);
4085 var events = expandoStore && expandoStore.events;
4086 var eventFns = events && events[eventName];
4087
4088 if (eventFns) {
4089 // Create a dummy event to pass to the handlers
4090 dummyEvent = {
4091 preventDefault: function() { this.defaultPrevented = true; },
4092 isDefaultPrevented: function() { return this.defaultPrevented === true; },
4093 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
4094 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
4095 stopPropagation: noop,
4096 type: eventName,
4097 target: element
4098 };
4099
4100 // If a custom event was provided then extend our dummy event with it
4101 if (event.type) {
4102 dummyEvent = extend(dummyEvent, event);
4103 }
4104
4105 // Copy event handlers in case event handlers array is modified during execution.
4106 eventFnsCopy = shallowCopy(eventFns);
4107 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
4108
4109 forEach(eventFnsCopy, function(fn) {
4110 if (!dummyEvent.isImmediatePropagationStopped()) {
4111 fn.apply(element, handlerArgs);
4112 }
4113 });
4114 }
4115 }
4116}, function(fn, name) {
4117 /**
4118 * chaining functions
4119 */
4120 JQLite.prototype[name] = function(arg1, arg2, arg3) {
4121 var value;
4122
4123 for (var i = 0, ii = this.length; i < ii; i++) {
4124 if (isUndefined(value)) {
4125 value = fn(this[i], arg1, arg2, arg3);
4126 if (isDefined(value)) {
4127 // any function which returns a value needs to be wrapped
4128 value = jqLite(value);
4129 }
4130 } else {
4131 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
4132 }
4133 }
4134 return isDefined(value) ? value : this;
4135 };
4136});
4137
4138// bind legacy bind/unbind to on/off
4139JQLite.prototype.bind = JQLite.prototype.on;
4140JQLite.prototype.unbind = JQLite.prototype.off;
4141
4142
4143// Provider for private $$jqLite service
4144/** @this */
4145function $$jqLiteProvider() {
4146 this.$get = function $$jqLite() {
4147 return extend(JQLite, {
4148 hasClass: function(node, classes) {
4149 if (node.attr) node = node[0];
4150 return jqLiteHasClass(node, classes);
4151 },
4152 addClass: function(node, classes) {
4153 if (node.attr) node = node[0];
4154 return jqLiteAddClass(node, classes);
4155 },
4156 removeClass: function(node, classes) {
4157 if (node.attr) node = node[0];
4158 return jqLiteRemoveClass(node, classes);
4159 }
4160 });
4161 };
4162}
4163
4164/**
4165 * Computes a hash of an 'obj'.
4166 * Hash of a:
4167 * string is string
4168 * number is number as string
4169 * object is either result of calling $$hashKey function on the object or uniquely generated id,
4170 * that is also assigned to the $$hashKey property of the object.
4171 *
4172 * @param obj
4173 * @returns {string} hash string such that the same input will have the same hash string.
4174 * The resulting string key is in 'type:hashKey' format.
4175 */
4176function hashKey(obj, nextUidFn) {
4177 var key = obj && obj.$$hashKey;
4178
4179 if (key) {
4180 if (typeof key === 'function') {
4181 key = obj.$$hashKey();
4182 }
4183 return key;
4184 }
4185
4186 var objType = typeof obj;
4187 if (objType === 'function' || (objType === 'object' && obj !== null)) {
4188 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
4189 } else {
4190 key = objType + ':' + obj;
4191 }
4192
4193 return key;
4194}
4195
4196// A minimal ES2015 Map implementation.
4197// Should be bug/feature equivalent to the native implementations of supported browsers
4198// (for the features required in Angular).
4199// See https://kangax.github.io/compat-table/es6/#test-Map
4200var nanKey = Object.create(null);
4201function NgMapShim() {
4202 this._keys = [];
4203 this._values = [];
4204 this._lastKey = NaN;
4205 this._lastIndex = -1;
4206}
4207NgMapShim.prototype = {
4208 _idx: function(key) {
4209 if (key !== this._lastKey) {
4210 this._lastKey = key;
4211 this._lastIndex = this._keys.indexOf(key);
4212 }
4213 return this._lastIndex;
4214 },
4215 _transformKey: function(key) {
4216 return isNumberNaN(key) ? nanKey : key;
4217 },
4218 get: function(key) {
4219 key = this._transformKey(key);
4220 var idx = this._idx(key);
4221 if (idx !== -1) {
4222 return this._values[idx];
4223 }
4224 },
4225 has: function(key) {
4226 key = this._transformKey(key);
4227 var idx = this._idx(key);
4228 return idx !== -1;
4229 },
4230 set: function(key, value) {
4231 key = this._transformKey(key);
4232 var idx = this._idx(key);
4233 if (idx === -1) {
4234 idx = this._lastIndex = this._keys.length;
4235 }
4236 this._keys[idx] = key;
4237 this._values[idx] = value;
4238
4239 // Support: IE11
4240 // Do not `return this` to simulate the partial IE11 implementation
4241 },
4242 delete: function(key) {
4243 key = this._transformKey(key);
4244 var idx = this._idx(key);
4245 if (idx === -1) {
4246 return false;
4247 }
4248 this._keys.splice(idx, 1);
4249 this._values.splice(idx, 1);
4250 this._lastKey = NaN;
4251 this._lastIndex = -1;
4252 return true;
4253 }
4254};
4255
4256// For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations
4257// are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map`
4258// implementations get more stable, we can reconsider switching to `window.Map` (when available).
4259var NgMap = NgMapShim;
4260
4261var $$MapProvider = [/** @this */function() {
4262 this.$get = [function() {
4263 return NgMap;
4264 }];
4265}];
4266
4267/**
4268 * @ngdoc function
4269 * @module ng
4270 * @name angular.injector
4271 * @kind function
4272 *
4273 * @description
4274 * Creates an injector object that can be used for retrieving services as well as for
4275 * dependency injection (see {@link guide/di dependency injection}).
4276 *
4277 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
4278 * {@link angular.module}. The `ng` module must be explicitly added.
4279 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
4280 * disallows argument name annotation inference.
4281 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
4282 *
4283 * @example
4284 * Typical usage
4285 * ```js
4286 * // create an injector
4287 * var $injector = angular.injector(['ng']);
4288 *
4289 * // use the injector to kick off your application
4290 * // use the type inference to auto inject arguments, or use implicit injection
4291 * $injector.invoke(function($rootScope, $compile, $document) {
4292 * $compile($document)($rootScope);
4293 * $rootScope.$digest();
4294 * });
4295 * ```
4296 *
4297 * Sometimes you want to get access to the injector of a currently running AngularJS app
4298 * from outside AngularJS. Perhaps, you want to inject and compile some markup after the
4299 * application has been bootstrapped. You can do this using the extra `injector()` added
4300 * to JQuery/jqLite elements. See {@link angular.element}.
4301 *
4302 * *This is fairly rare but could be the case if a third party library is injecting the
4303 * markup.*
4304 *
4305 * In the following example a new block of HTML containing a `ng-controller`
4306 * directive is added to the end of the document body by JQuery. We then compile and link
4307 * it into the current AngularJS scope.
4308 *
4309 * ```js
4310 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
4311 * $(document.body).append($div);
4312 *
4313 * angular.element(document).injector().invoke(function($compile) {
4314 * var scope = angular.element($div).scope();
4315 * $compile($div)(scope);
4316 * });
4317 * ```
4318 */
4319
4320
4321/**
4322 * @ngdoc module
4323 * @name auto
4324 * @installation
4325 * @description
4326 *
4327 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
4328 */
4329
4330var ARROW_ARG = /^([^(]+?)=>/;
4331var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m;
4332var FN_ARG_SPLIT = /,/;
4333var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
4334var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
4335var $injectorMinErr = minErr('$injector');
4336
4337function stringifyFn(fn) {
4338 return Function.prototype.toString.call(fn);
4339}
4340
4341function extractArgs(fn) {
4342 var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''),
4343 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
4344 return args;
4345}
4346
4347function anonFn(fn) {
4348 // For anonymous functions, showing at the very least the function signature can help in
4349 // debugging.
4350 var args = extractArgs(fn);
4351 if (args) {
4352 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
4353 }
4354 return 'fn';
4355}
4356
4357function annotate(fn, strictDi, name) {
4358 var $inject,
4359 argDecl,
4360 last;
4361
4362 if (typeof fn === 'function') {
4363 if (!($inject = fn.$inject)) {
4364 $inject = [];
4365 if (fn.length) {
4366 if (strictDi) {
4367 if (!isString(name) || !name) {
4368 name = fn.name || anonFn(fn);
4369 }
4370 throw $injectorMinErr('strictdi',
4371 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
4372 }
4373 argDecl = extractArgs(fn);
4374 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
4375 arg.replace(FN_ARG, function(all, underscore, name) {
4376 $inject.push(name);
4377 });
4378 });
4379 }
4380 fn.$inject = $inject;
4381 }
4382 } else if (isArray(fn)) {
4383 last = fn.length - 1;
4384 assertArgFn(fn[last], 'fn');
4385 $inject = fn.slice(0, last);
4386 } else {
4387 assertArgFn(fn, 'fn', true);
4388 }
4389 return $inject;
4390}
4391
4392///////////////////////////////////////
4393
4394/**
4395 * @ngdoc service
4396 * @name $injector
4397 *
4398 * @description
4399 *
4400 * `$injector` is used to retrieve object instances as defined by
4401 * {@link auto.$provide provider}, instantiate types, invoke methods,
4402 * and load modules.
4403 *
4404 * The following always holds true:
4405 *
4406 * ```js
4407 * var $injector = angular.injector();
4408 * expect($injector.get('$injector')).toBe($injector);
4409 * expect($injector.invoke(function($injector) {
4410 * return $injector;
4411 * })).toBe($injector);
4412 * ```
4413 *
4414 * ## Injection Function Annotation
4415 *
4416 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
4417 * following are all valid ways of annotating function with injection arguments and are equivalent.
4418 *
4419 * ```js
4420 * // inferred (only works if code not minified/obfuscated)
4421 * $injector.invoke(function(serviceA){});
4422 *
4423 * // annotated
4424 * function explicit(serviceA) {};
4425 * explicit.$inject = ['serviceA'];
4426 * $injector.invoke(explicit);
4427 *
4428 * // inline
4429 * $injector.invoke(['serviceA', function(serviceA){}]);
4430 * ```
4431 *
4432 * ### Inference
4433 *
4434 * In JavaScript calling `toString()` on a function returns the function definition. The definition
4435 * can then be parsed and the function arguments can be extracted. This method of discovering
4436 * annotations is disallowed when the injector is in strict mode.
4437 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
4438 * argument names.
4439 *
4440 * ### `$inject` Annotation
4441 * By adding an `$inject` property onto a function the injection parameters can be specified.
4442 *
4443 * ### Inline
4444 * As an array of injection names, where the last item in the array is the function to call.
4445 */
4446
4447/**
4448 * @ngdoc property
4449 * @name $injector#modules
4450 * @type {Object}
4451 * @description
4452 * A hash containing all the modules that have been loaded into the
4453 * $injector.
4454 *
4455 * You can use this property to find out information about a module via the
4456 * {@link angular.Module#info `myModule.info(...)`} method.
4457 *
4458 * For example:
4459 *
4460 * ```
4461 * var info = $injector.modules['ngAnimate'].info();
4462 * ```
4463 *
4464 * **Do not use this property to attempt to modify the modules after the application
4465 * has been bootstrapped.**
4466 */
4467
4468
4469/**
4470 * @ngdoc method
4471 * @name $injector#get
4472 *
4473 * @description
4474 * Return an instance of the service.
4475 *
4476 * @param {string} name The name of the instance to retrieve.
4477 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
4478 * @return {*} The instance.
4479 */
4480
4481/**
4482 * @ngdoc method
4483 * @name $injector#invoke
4484 *
4485 * @description
4486 * Invoke the method and supply the method arguments from the `$injector`.
4487 *
4488 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
4489 * injected according to the {@link guide/di $inject Annotation} rules.
4490 * @param {Object=} self The `this` for the invoked method.
4491 * @param {Object=} locals Optional object. If preset then any argument names are read from this
4492 * object first, before the `$injector` is consulted.
4493 * @returns {*} the value returned by the invoked `fn` function.
4494 */
4495
4496/**
4497 * @ngdoc method
4498 * @name $injector#has
4499 *
4500 * @description
4501 * Allows the user to query if the particular service exists.
4502 *
4503 * @param {string} name Name of the service to query.
4504 * @returns {boolean} `true` if injector has given service.
4505 */
4506
4507/**
4508 * @ngdoc method
4509 * @name $injector#instantiate
4510 * @description
4511 * Create a new instance of JS type. The method takes a constructor function, invokes the new
4512 * operator, and supplies all of the arguments to the constructor function as specified by the
4513 * constructor annotation.
4514 *
4515 * @param {Function} Type Annotated constructor function.
4516 * @param {Object=} locals Optional object. If preset then any argument names are read from this
4517 * object first, before the `$injector` is consulted.
4518 * @returns {Object} new instance of `Type`.
4519 */
4520
4521/**
4522 * @ngdoc method
4523 * @name $injector#annotate
4524 *
4525 * @description
4526 * Returns an array of service names which the function is requesting for injection. This API is
4527 * used by the injector to determine which services need to be injected into the function when the
4528 * function is invoked. There are three ways in which the function can be annotated with the needed
4529 * dependencies.
4530 *
4531 * #### Argument names
4532 *
4533 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4534 * by converting the function into a string using `toString()` method and extracting the argument
4535 * names.
4536 * ```js
4537 * // Given
4538 * function MyController($scope, $route) {
4539 * // ...
4540 * }
4541 *
4542 * // Then
4543 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4544 * ```
4545 *
4546 * You can disallow this method by using strict injection mode.
4547 *
4548 * This method does not work with code minification / obfuscation. For this reason the following
4549 * annotation strategies are supported.
4550 *
4551 * #### The `$inject` property
4552 *
4553 * If a function has an `$inject` property and its value is an array of strings, then the strings
4554 * represent names of services to be injected into the function.
4555 * ```js
4556 * // Given
4557 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4558 * // ...
4559 * }
4560 * // Define function dependencies
4561 * MyController['$inject'] = ['$scope', '$route'];
4562 *
4563 * // Then
4564 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4565 * ```
4566 *
4567 * #### The array notation
4568 *
4569 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4570 * is very inconvenient. In these situations using the array notation to specify the dependencies in
4571 * a way that survives minification is a better choice:
4572 *
4573 * ```js
4574 * // We wish to write this (not minification / obfuscation safe)
4575 * injector.invoke(function($compile, $rootScope) {
4576 * // ...
4577 * });
4578 *
4579 * // We are forced to write break inlining
4580 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4581 * // ...
4582 * };
4583 * tmpFn.$inject = ['$compile', '$rootScope'];
4584 * injector.invoke(tmpFn);
4585 *
4586 * // To better support inline function the inline annotation is supported
4587 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4588 * // ...
4589 * }]);
4590 *
4591 * // Therefore
4592 * expect(injector.annotate(
4593 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4594 * ).toEqual(['$compile', '$rootScope']);
4595 * ```
4596 *
4597 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4598 * be retrieved as described above.
4599 *
4600 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4601 *
4602 * @returns {Array.<string>} The names of the services which the function requires.
4603 */
4604/**
4605 * @ngdoc method
4606 * @name $injector#loadNewModules
4607 *
4608 * @description
4609 *
4610 * **This is a dangerous API, which you use at your own risk!**
4611 *
4612 * Add the specified modules to the current injector.
4613 *
4614 * This method will add each of the injectables to the injector and execute all of the config and run
4615 * blocks for each module passed to the method.
4616 *
4617 * If a module has already been loaded into the injector then it will not be loaded again.
4618 *
4619 * * The application developer is responsible for loading the code containing the modules; and for
4620 * ensuring that lazy scripts are not downloaded and executed more often that desired.
4621 * * Previously compiled HTML will not be affected by newly loaded directives, filters and components.
4622 * * Modules cannot be unloaded.
4623 *
4624 * You can use {@link $injector#modules `$injector.modules`} to check whether a module has been loaded
4625 * into the injector, which may indicate whether the script has been executed already.
4626 *
4627 * @example
4628 * Here is an example of loading a bundle of modules, with a utility method called `getScript`:
4629 *
4630 * ```javascript
4631 * app.factory('loadModule', function($injector) {
4632 * return function loadModule(moduleName, bundleUrl) {
4633 * return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); });
4634 * };
4635 * })
4636 * ```
4637 *
4638 * @param {Array<String|Function|Array>=} mods an array of modules to load into the application.
4639 * Each item in the array should be the name of a predefined module or a (DI annotated)
4640 * function that will be invoked by the injector as a `config` block.
4641 * See: {@link angular.module modules}
4642 */
4643
4644
4645/**
4646 * @ngdoc service
4647 * @name $provide
4648 *
4649 * @description
4650 *
4651 * The {@link auto.$provide $provide} service has a number of methods for registering components
4652 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4653 * {@link angular.Module}.
4654 *
4655 * An AngularJS **service** is a singleton object created by a **service factory**. These **service
4656 * factories** are functions which, in turn, are created by a **service provider**.
4657 * The **service providers** are constructor functions. When instantiated they must contain a
4658 * property called `$get`, which holds the **service factory** function.
4659 *
4660 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4661 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4662 * function to get the instance of the **service**.
4663 *
4664 * Often services have no configuration options and there is no need to add methods to the service
4665 * provider. The provider will be no more than a constructor function with a `$get` property. For
4666 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4667 * services without specifying a provider.
4668 *
4669 * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the
4670 * {@link auto.$injector $injector}
4671 * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by
4672 * providers and services.
4673 * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by
4674 * services, not providers.
4675 * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function**
4676 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4677 * given factory function.
4678 * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function**
4679 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4680 * a new object using the given constructor function.
4681 * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that
4682 * will be able to modify or replace the implementation of another service.
4683 *
4684 * See the individual methods for more information and examples.
4685 */
4686
4687/**
4688 * @ngdoc method
4689 * @name $provide#provider
4690 * @description
4691 *
4692 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4693 * are constructor functions, whose instances are responsible for "providing" a factory for a
4694 * service.
4695 *
4696 * Service provider names start with the name of the service they provide followed by `Provider`.
4697 * For example, the {@link ng.$log $log} service has a provider called
4698 * {@link ng.$logProvider $logProvider}.
4699 *
4700 * Service provider objects can have additional methods which allow configuration of the provider
4701 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4702 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4703 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4704 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4705 * console or not.
4706 *
4707 * It is possible to inject other providers into the provider function,
4708 * but the injected provider must have been defined before the one that requires it.
4709 *
4710 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4711 'Provider'` key.
4712 * @param {(Object|function())} provider If the provider is:
4713 *
4714 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4715 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4716 * - `Constructor`: a new instance of the provider will be created using
4717 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4718 *
4719 * @returns {Object} registered provider instance
4720
4721 * @example
4722 *
4723 * The following example shows how to create a simple event tracking service and register it using
4724 * {@link auto.$provide#provider $provide.provider()}.
4725 *
4726 * ```js
4727 * // Define the eventTracker provider
4728 * function EventTrackerProvider() {
4729 * var trackingUrl = '/track';
4730 *
4731 * // A provider method for configuring where the tracked events should been saved
4732 * this.setTrackingUrl = function(url) {
4733 * trackingUrl = url;
4734 * };
4735 *
4736 * // The service factory function
4737 * this.$get = ['$http', function($http) {
4738 * var trackedEvents = {};
4739 * return {
4740 * // Call this to track an event
4741 * event: function(event) {
4742 * var count = trackedEvents[event] || 0;
4743 * count += 1;
4744 * trackedEvents[event] = count;
4745 * return count;
4746 * },
4747 * // Call this to save the tracked events to the trackingUrl
4748 * save: function() {
4749 * $http.post(trackingUrl, trackedEvents);
4750 * }
4751 * };
4752 * }];
4753 * }
4754 *
4755 * describe('eventTracker', function() {
4756 * var postSpy;
4757 *
4758 * beforeEach(module(function($provide) {
4759 * // Register the eventTracker provider
4760 * $provide.provider('eventTracker', EventTrackerProvider);
4761 * }));
4762 *
4763 * beforeEach(module(function(eventTrackerProvider) {
4764 * // Configure eventTracker provider
4765 * eventTrackerProvider.setTrackingUrl('/custom-track');
4766 * }));
4767 *
4768 * it('tracks events', inject(function(eventTracker) {
4769 * expect(eventTracker.event('login')).toEqual(1);
4770 * expect(eventTracker.event('login')).toEqual(2);
4771 * }));
4772 *
4773 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4774 * postSpy = spyOn($http, 'post');
4775 * eventTracker.event('login');
4776 * eventTracker.save();
4777 * expect(postSpy).toHaveBeenCalled();
4778 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4779 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4780 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4781 * }));
4782 * });
4783 * ```
4784 */
4785
4786/**
4787 * @ngdoc method
4788 * @name $provide#factory
4789 * @description
4790 *
4791 * Register a **service factory**, which will be called to return the service instance.
4792 * This is short for registering a service where its provider consists of only a `$get` property,
4793 * which is the given service factory function.
4794 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4795 * configure your service in a provider.
4796 *
4797 * @param {string} name The name of the instance.
4798 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4799 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4800 * @returns {Object} registered provider instance
4801 *
4802 * @example
4803 * Here is an example of registering a service
4804 * ```js
4805 * $provide.factory('ping', ['$http', function($http) {
4806 * return function ping() {
4807 * return $http.send('/ping');
4808 * };
4809 * }]);
4810 * ```
4811 * You would then inject and use this service like this:
4812 * ```js
4813 * someModule.controller('Ctrl', ['ping', function(ping) {
4814 * ping();
4815 * }]);
4816 * ```
4817 */
4818
4819
4820/**
4821 * @ngdoc method
4822 * @name $provide#service
4823 * @description
4824 *
4825 * Register a **service constructor**, which will be invoked with `new` to create the service
4826 * instance.
4827 * This is short for registering a service where its provider's `$get` property is a factory
4828 * function that returns an instance instantiated by the injector from the service constructor
4829 * function.
4830 *
4831 * Internally it looks a bit like this:
4832 *
4833 * ```
4834 * {
4835 * $get: function() {
4836 * return $injector.instantiate(constructor);
4837 * }
4838 * }
4839 * ```
4840 *
4841 *
4842 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4843 * as a type/class.
4844 *
4845 * @param {string} name The name of the instance.
4846 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4847 * that will be instantiated.
4848 * @returns {Object} registered provider instance
4849 *
4850 * @example
4851 * Here is an example of registering a service using
4852 * {@link auto.$provide#service $provide.service(class)}.
4853 * ```js
4854 * var Ping = function($http) {
4855 * this.$http = $http;
4856 * };
4857 *
4858 * Ping.$inject = ['$http'];
4859 *
4860 * Ping.prototype.send = function() {
4861 * return this.$http.get('/ping');
4862 * };
4863 * $provide.service('ping', Ping);
4864 * ```
4865 * You would then inject and use this service like this:
4866 * ```js
4867 * someModule.controller('Ctrl', ['ping', function(ping) {
4868 * ping.send();
4869 * }]);
4870 * ```
4871 */
4872
4873
4874/**
4875 * @ngdoc method
4876 * @name $provide#value
4877 * @description
4878 *
4879 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4880 * number, an array, an object or a function. This is short for registering a service where its
4881 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4882 * service**. That also means it is not possible to inject other services into a value service.
4883 *
4884 * Value services are similar to constant services, except that they cannot be injected into a
4885 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4886 * an AngularJS {@link auto.$provide#decorator decorator}.
4887 *
4888 * @param {string} name The name of the instance.
4889 * @param {*} value The value.
4890 * @returns {Object} registered provider instance
4891 *
4892 * @example
4893 * Here are some examples of creating value services.
4894 * ```js
4895 * $provide.value('ADMIN_USER', 'admin');
4896 *
4897 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4898 *
4899 * $provide.value('halfOf', function(value) {
4900 * return value / 2;
4901 * });
4902 * ```
4903 */
4904
4905
4906/**
4907 * @ngdoc method
4908 * @name $provide#constant
4909 * @description
4910 *
4911 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string,
4912 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not
4913 * possible to inject other services into a constant.
4914 *
4915 * But unlike {@link auto.$provide#value value}, a constant can be
4916 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4917 * be overridden by an AngularJS {@link auto.$provide#decorator decorator}.
4918 *
4919 * @param {string} name The name of the constant.
4920 * @param {*} value The constant value.
4921 * @returns {Object} registered instance
4922 *
4923 * @example
4924 * Here a some examples of creating constants:
4925 * ```js
4926 * $provide.constant('SHARD_HEIGHT', 306);
4927 *
4928 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4929 *
4930 * $provide.constant('double', function(value) {
4931 * return value * 2;
4932 * });
4933 * ```
4934 */
4935
4936
4937/**
4938 * @ngdoc method
4939 * @name $provide#decorator
4940 * @description
4941 *
4942 * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function
4943 * intercepts the creation of a service, allowing it to override or modify the behavior of the
4944 * service. The return value of the decorator function may be the original service, or a new service
4945 * that replaces (or wraps and delegates to) the original service.
4946 *
4947 * You can find out more about using decorators in the {@link guide/decorators} guide.
4948 *
4949 * @param {string} name The name of the service to decorate.
4950 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4951 * provided and should return the decorated service instance. The function is called using
4952 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4953 * Local injection arguments:
4954 *
4955 * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured,
4956 * decorated or delegated to.
4957 *
4958 * @example
4959 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4960 * calls to {@link ng.$log#error $log.warn()}.
4961 * ```js
4962 * $provide.decorator('$log', ['$delegate', function($delegate) {
4963 * $delegate.warn = $delegate.error;
4964 * return $delegate;
4965 * }]);
4966 * ```
4967 */
4968
4969
4970function createInjector(modulesToLoad, strictDi) {
4971 strictDi = (strictDi === true);
4972 var INSTANTIATING = {},
4973 providerSuffix = 'Provider',
4974 path = [],
4975 loadedModules = new NgMap(),
4976 providerCache = {
4977 $provide: {
4978 provider: supportObject(provider),
4979 factory: supportObject(factory),
4980 service: supportObject(service),
4981 value: supportObject(value),
4982 constant: supportObject(constant),
4983 decorator: decorator
4984 }
4985 },
4986 providerInjector = (providerCache.$injector =
4987 createInternalInjector(providerCache, function(serviceName, caller) {
4988 if (angular.isString(caller)) {
4989 path.push(caller);
4990 }
4991 throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- '));
4992 })),
4993 instanceCache = {},
4994 protoInstanceInjector =
4995 createInternalInjector(instanceCache, function(serviceName, caller) {
4996 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4997 return instanceInjector.invoke(
4998 provider.$get, provider, undefined, serviceName);
4999 }),
5000 instanceInjector = protoInstanceInjector;
5001
5002 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
5003 instanceInjector.modules = providerInjector.modules = createMap();
5004 var runBlocks = loadModules(modulesToLoad);
5005 instanceInjector = protoInstanceInjector.get('$injector');
5006 instanceInjector.strictDi = strictDi;
5007 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
5008
5009 instanceInjector.loadNewModules = function(mods) {
5010 forEach(loadModules(mods), function(fn) { if (fn) instanceInjector.invoke(fn); });
5011 };
5012
5013
5014 return instanceInjector;
5015
5016 ////////////////////////////////////
5017 // $provider
5018 ////////////////////////////////////
5019
5020 function supportObject(delegate) {
5021 return function(key, value) {
5022 if (isObject(key)) {
5023 forEach(key, reverseParams(delegate));
5024 } else {
5025 return delegate(key, value);
5026 }
5027 };
5028 }
5029
5030 function provider(name, provider_) {
5031 assertNotHasOwnProperty(name, 'service');
5032 if (isFunction(provider_) || isArray(provider_)) {
5033 provider_ = providerInjector.instantiate(provider_);
5034 }
5035 if (!provider_.$get) {
5036 throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name);
5037 }
5038 return (providerCache[name + providerSuffix] = provider_);
5039 }
5040
5041 function enforceReturnValue(name, factory) {
5042 return /** @this */ function enforcedReturnValue() {
5043 var result = instanceInjector.invoke(factory, this);
5044 if (isUndefined(result)) {
5045 throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name);
5046 }
5047 return result;
5048 };
5049 }
5050
5051 function factory(name, factoryFn, enforce) {
5052 return provider(name, {
5053 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
5054 });
5055 }
5056
5057 function service(name, constructor) {
5058 return factory(name, ['$injector', function($injector) {
5059 return $injector.instantiate(constructor);
5060 }]);
5061 }
5062
5063 function value(name, val) { return factory(name, valueFn(val), false); }
5064
5065 function constant(name, value) {
5066 assertNotHasOwnProperty(name, 'constant');
5067 providerCache[name] = value;
5068 instanceCache[name] = value;
5069 }
5070
5071 function decorator(serviceName, decorFn) {
5072 var origProvider = providerInjector.get(serviceName + providerSuffix),
5073 orig$get = origProvider.$get;
5074
5075 origProvider.$get = function() {
5076 var origInstance = instanceInjector.invoke(orig$get, origProvider);
5077 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
5078 };
5079 }
5080
5081 ////////////////////////////////////
5082 // Module Loading
5083 ////////////////////////////////////
5084 function loadModules(modulesToLoad) {
5085 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
5086 var runBlocks = [], moduleFn;
5087 forEach(modulesToLoad, function(module) {
5088 if (loadedModules.get(module)) return;
5089 loadedModules.set(module, true);
5090
5091 function runInvokeQueue(queue) {
5092 var i, ii;
5093 for (i = 0, ii = queue.length; i < ii; i++) {
5094 var invokeArgs = queue[i],
5095 provider = providerInjector.get(invokeArgs[0]);
5096
5097 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
5098 }
5099 }
5100
5101 try {
5102 if (isString(module)) {
5103 moduleFn = angularModule(module);
5104 instanceInjector.modules[module] = moduleFn;
5105 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
5106 runInvokeQueue(moduleFn._invokeQueue);
5107 runInvokeQueue(moduleFn._configBlocks);
5108 } else if (isFunction(module)) {
5109 runBlocks.push(providerInjector.invoke(module));
5110 } else if (isArray(module)) {
5111 runBlocks.push(providerInjector.invoke(module));
5112 } else {
5113 assertArgFn(module, 'module');
5114 }
5115 } catch (e) {
5116 if (isArray(module)) {
5117 module = module[module.length - 1];
5118 }
5119 if (e.message && e.stack && e.stack.indexOf(e.message) === -1) {
5120 // Safari & FF's stack traces don't contain error.message content
5121 // unlike those of Chrome and IE
5122 // So if stack doesn't contain message, we create a new string that contains both.
5123 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
5124 // eslint-disable-next-line no-ex-assign
5125 e = e.message + '\n' + e.stack;
5126 }
5127 throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}',
5128 module, e.stack || e.message || e);
5129 }
5130 });
5131 return runBlocks;
5132 }
5133
5134 ////////////////////////////////////
5135 // internal Injector
5136 ////////////////////////////////////
5137
5138 function createInternalInjector(cache, factory) {
5139
5140 function getService(serviceName, caller) {
5141 if (cache.hasOwnProperty(serviceName)) {
5142 if (cache[serviceName] === INSTANTIATING) {
5143 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
5144 serviceName + ' <- ' + path.join(' <- '));
5145 }
5146 return cache[serviceName];
5147 } else {
5148 try {
5149 path.unshift(serviceName);
5150 cache[serviceName] = INSTANTIATING;
5151 cache[serviceName] = factory(serviceName, caller);
5152 return cache[serviceName];
5153 } catch (err) {
5154 if (cache[serviceName] === INSTANTIATING) {
5155 delete cache[serviceName];
5156 }
5157 throw err;
5158 } finally {
5159 path.shift();
5160 }
5161 }
5162 }
5163
5164
5165 function injectionArgs(fn, locals, serviceName) {
5166 var args = [],
5167 $inject = createInjector.$$annotate(fn, strictDi, serviceName);
5168
5169 for (var i = 0, length = $inject.length; i < length; i++) {
5170 var key = $inject[i];
5171 if (typeof key !== 'string') {
5172 throw $injectorMinErr('itkn',
5173 'Incorrect injection token! Expected service name as string, got {0}', key);
5174 }
5175 args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
5176 getService(key, serviceName));
5177 }
5178 return args;
5179 }
5180
5181 function isClass(func) {
5182 // Support: IE 9-11 only
5183 // IE 9-11 do not support classes and IE9 leaks with the code below.
5184 if (msie || typeof func !== 'function') {
5185 return false;
5186 }
5187 var result = func.$$ngIsClass;
5188 if (!isBoolean(result)) {
5189 result = func.$$ngIsClass = /^class\b/.test(stringifyFn(func));
5190 }
5191 return result;
5192 }
5193
5194 function invoke(fn, self, locals, serviceName) {
5195 if (typeof locals === 'string') {
5196 serviceName = locals;
5197 locals = null;
5198 }
5199
5200 var args = injectionArgs(fn, locals, serviceName);
5201 if (isArray(fn)) {
5202 fn = fn[fn.length - 1];
5203 }
5204
5205 if (!isClass(fn)) {
5206 // http://jsperf.com/angularjs-invoke-apply-vs-switch
5207 // #5388
5208 return fn.apply(self, args);
5209 } else {
5210 args.unshift(null);
5211 return new (Function.prototype.bind.apply(fn, args))();
5212 }
5213 }
5214
5215
5216 function instantiate(Type, locals, serviceName) {
5217 // Check if Type is annotated and use just the given function at n-1 as parameter
5218 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
5219 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
5220 var args = injectionArgs(Type, locals, serviceName);
5221 // Empty object at position 0 is ignored for invocation with `new`, but required.
5222 args.unshift(null);
5223 return new (Function.prototype.bind.apply(ctor, args))();
5224 }
5225
5226
5227 return {
5228 invoke: invoke,
5229 instantiate: instantiate,
5230 get: getService,
5231 annotate: createInjector.$$annotate,
5232 has: function(name) {
5233 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
5234 }
5235 };
5236 }
5237}
5238
5239createInjector.$$annotate = annotate;
5240
5241/**
5242 * @ngdoc provider
5243 * @name $anchorScrollProvider
5244 * @this
5245 *
5246 * @description
5247 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
5248 * {@link ng.$location#hash $location.hash()} changes.
5249 */
5250function $AnchorScrollProvider() {
5251
5252 var autoScrollingEnabled = true;
5253
5254 /**
5255 * @ngdoc method
5256 * @name $anchorScrollProvider#disableAutoScrolling
5257 *
5258 * @description
5259 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
5260 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
5261 * Use this method to disable automatic scrolling.
5262 *
5263 * If automatic scrolling is disabled, one must explicitly call
5264 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
5265 * current hash.
5266 */
5267 this.disableAutoScrolling = function() {
5268 autoScrollingEnabled = false;
5269 };
5270
5271 /**
5272 * @ngdoc service
5273 * @name $anchorScroll
5274 * @kind function
5275 * @requires $window
5276 * @requires $location
5277 * @requires $rootScope
5278 *
5279 * @description
5280 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
5281 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
5282 * in the
5283 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document).
5284 *
5285 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
5286 * match any anchor whenever it changes. This can be disabled by calling
5287 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
5288 *
5289 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
5290 * vertical scroll-offset (either fixed or dynamic).
5291 *
5292 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
5293 * {@link ng.$location#hash $location.hash()} will be used.
5294 *
5295 * @property {(number|function|jqLite)} yOffset
5296 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
5297 * positioned elements at the top of the page, such as navbars, headers etc.
5298 *
5299 * `yOffset` can be specified in various ways:
5300 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
5301 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
5302 * a number representing the offset (in pixels).<br /><br />
5303 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
5304 * the top of the page to the element's bottom will be used as offset.<br />
5305 * **Note**: The element will be taken into account only as long as its `position` is set to
5306 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
5307 * their height and/or positioning according to the viewport's size.
5308 *
5309 * <br />
5310 * <div class="alert alert-warning">
5311 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
5312 * not some child element.
5313 * </div>
5314 *
5315 * @example
5316 <example module="anchorScrollExample" name="anchor-scroll">
5317 <file name="index.html">
5318 <div id="scrollArea" ng-controller="ScrollController">
5319 <a ng-click="gotoBottom()">Go to bottom</a>
5320 <a id="bottom"></a> You're at the bottom!
5321 </div>
5322 </file>
5323 <file name="script.js">
5324 angular.module('anchorScrollExample', [])
5325 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
5326 function($scope, $location, $anchorScroll) {
5327 $scope.gotoBottom = function() {
5328 // set the location.hash to the id of
5329 // the element you wish to scroll to.
5330 $location.hash('bottom');
5331
5332 // call $anchorScroll()
5333 $anchorScroll();
5334 };
5335 }]);
5336 </file>
5337 <file name="style.css">
5338 #scrollArea {
5339 height: 280px;
5340 overflow: auto;
5341 }
5342
5343 #bottom {
5344 display: block;
5345 margin-top: 2000px;
5346 }
5347 </file>
5348 </example>
5349 *
5350 * <hr />
5351 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
5352 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
5353 *
5354 * @example
5355 <example module="anchorScrollOffsetExample" name="anchor-scroll-offset">
5356 <file name="index.html">
5357 <div class="fixed-header" ng-controller="headerCtrl">
5358 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
5359 Go to anchor {{x}}
5360 </a>
5361 </div>
5362 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
5363 Anchor {{x}} of 5
5364 </div>
5365 </file>
5366 <file name="script.js">
5367 angular.module('anchorScrollOffsetExample', [])
5368 .run(['$anchorScroll', function($anchorScroll) {
5369 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
5370 }])
5371 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
5372 function($anchorScroll, $location, $scope) {
5373 $scope.gotoAnchor = function(x) {
5374 var newHash = 'anchor' + x;
5375 if ($location.hash() !== newHash) {
5376 // set the $location.hash to `newHash` and
5377 // $anchorScroll will automatically scroll to it
5378 $location.hash('anchor' + x);
5379 } else {
5380 // call $anchorScroll() explicitly,
5381 // since $location.hash hasn't changed
5382 $anchorScroll();
5383 }
5384 };
5385 }
5386 ]);
5387 </file>
5388 <file name="style.css">
5389 body {
5390 padding-top: 50px;
5391 }
5392
5393 .anchor {
5394 border: 2px dashed DarkOrchid;
5395 padding: 10px 10px 200px 10px;
5396 }
5397
5398 .fixed-header {
5399 background-color: rgba(0, 0, 0, 0.2);
5400 height: 50px;
5401 position: fixed;
5402 top: 0; left: 0; right: 0;
5403 }
5404
5405 .fixed-header > a {
5406 display: inline-block;
5407 margin: 5px 15px;
5408 }
5409 </file>
5410 </example>
5411 */
5412 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
5413 var document = $window.document;
5414
5415 // Helper function to get first anchor from a NodeList
5416 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
5417 // and working in all supported browsers.)
5418 function getFirstAnchor(list) {
5419 var result = null;
5420 Array.prototype.some.call(list, function(element) {
5421 if (nodeName_(element) === 'a') {
5422 result = element;
5423 return true;
5424 }
5425 });
5426 return result;
5427 }
5428
5429 function getYOffset() {
5430
5431 var offset = scroll.yOffset;
5432
5433 if (isFunction(offset)) {
5434 offset = offset();
5435 } else if (isElement(offset)) {
5436 var elem = offset[0];
5437 var style = $window.getComputedStyle(elem);
5438 if (style.position !== 'fixed') {
5439 offset = 0;
5440 } else {
5441 offset = elem.getBoundingClientRect().bottom;
5442 }
5443 } else if (!isNumber(offset)) {
5444 offset = 0;
5445 }
5446
5447 return offset;
5448 }
5449
5450 function scrollTo(elem) {
5451 if (elem) {
5452 elem.scrollIntoView();
5453
5454 var offset = getYOffset();
5455
5456 if (offset) {
5457 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
5458 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
5459 // top of the viewport.
5460 //
5461 // IF the number of pixels from the top of `elem` to the end of the page's content is less
5462 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
5463 // way down the page.
5464 //
5465 // This is often the case for elements near the bottom of the page.
5466 //
5467 // In such cases we do not need to scroll the whole `offset` up, just the difference between
5468 // the top of the element and the offset, which is enough to align the top of `elem` at the
5469 // desired position.
5470 var elemTop = elem.getBoundingClientRect().top;
5471 $window.scrollBy(0, elemTop - offset);
5472 }
5473 } else {
5474 $window.scrollTo(0, 0);
5475 }
5476 }
5477
5478 function scroll(hash) {
5479 // Allow numeric hashes
5480 hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash();
5481 var elm;
5482
5483 // empty hash, scroll to the top of the page
5484 if (!hash) scrollTo(null);
5485
5486 // element with given id
5487 else if ((elm = document.getElementById(hash))) scrollTo(elm);
5488
5489 // first anchor with given name :-D
5490 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
5491
5492 // no element and hash === 'top', scroll to the top of the page
5493 else if (hash === 'top') scrollTo(null);
5494 }
5495
5496 // does not scroll when user clicks on anchor link that is currently on
5497 // (no url change, no $location.hash() change), browser native does scroll
5498 if (autoScrollingEnabled) {
5499 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
5500 function autoScrollWatchAction(newVal, oldVal) {
5501 // skip the initial scroll if $location.hash is empty
5502 if (newVal === oldVal && newVal === '') return;
5503
5504 jqLiteDocumentLoaded(function() {
5505 $rootScope.$evalAsync(scroll);
5506 });
5507 });
5508 }
5509
5510 return scroll;
5511 }];
5512}
5513
5514var $animateMinErr = minErr('$animate');
5515var ELEMENT_NODE = 1;
5516var NG_ANIMATE_CLASSNAME = 'ng-animate';
5517
5518function mergeClasses(a,b) {
5519 if (!a && !b) return '';
5520 if (!a) return b;
5521 if (!b) return a;
5522 if (isArray(a)) a = a.join(' ');
5523 if (isArray(b)) b = b.join(' ');
5524 return a + ' ' + b;
5525}
5526
5527function extractElementNode(element) {
5528 for (var i = 0; i < element.length; i++) {
5529 var elm = element[i];
5530 if (elm.nodeType === ELEMENT_NODE) {
5531 return elm;
5532 }
5533 }
5534}
5535
5536function splitClasses(classes) {
5537 if (isString(classes)) {
5538 classes = classes.split(' ');
5539 }
5540
5541 // Use createMap() to prevent class assumptions involving property names in
5542 // Object.prototype
5543 var obj = createMap();
5544 forEach(classes, function(klass) {
5545 // sometimes the split leaves empty string values
5546 // incase extra spaces were applied to the options
5547 if (klass.length) {
5548 obj[klass] = true;
5549 }
5550 });
5551 return obj;
5552}
5553
5554// if any other type of options value besides an Object value is
5555// passed into the $animate.method() animation then this helper code
5556// will be run which will ignore it. While this patch is not the
5557// greatest solution to this, a lot of existing plugins depend on
5558// $animate to either call the callback (< 1.2) or return a promise
5559// that can be changed. This helper function ensures that the options
5560// are wiped clean incase a callback function is provided.
5561function prepareAnimateOptions(options) {
5562 return isObject(options)
5563 ? options
5564 : {};
5565}
5566
5567var $$CoreAnimateJsProvider = /** @this */ function() {
5568 this.$get = noop;
5569};
5570
5571// this is prefixed with Core since it conflicts with
5572// the animateQueueProvider defined in ngAnimate/animateQueue.js
5573var $$CoreAnimateQueueProvider = /** @this */ function() {
5574 var postDigestQueue = new NgMap();
5575 var postDigestElements = [];
5576
5577 this.$get = ['$$AnimateRunner', '$rootScope',
5578 function($$AnimateRunner, $rootScope) {
5579 return {
5580 enabled: noop,
5581 on: noop,
5582 off: noop,
5583 pin: noop,
5584
5585 push: function(element, event, options, domOperation) {
5586 if (domOperation) {
5587 domOperation();
5588 }
5589
5590 options = options || {};
5591 if (options.from) {
5592 element.css(options.from);
5593 }
5594 if (options.to) {
5595 element.css(options.to);
5596 }
5597
5598 if (options.addClass || options.removeClass) {
5599 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
5600 }
5601
5602 var runner = new $$AnimateRunner();
5603
5604 // since there are no animations to run the runner needs to be
5605 // notified that the animation call is complete.
5606 runner.complete();
5607 return runner;
5608 }
5609 };
5610
5611
5612 function updateData(data, classes, value) {
5613 var changed = false;
5614 if (classes) {
5615 classes = isString(classes) ? classes.split(' ') :
5616 isArray(classes) ? classes : [];
5617 forEach(classes, function(className) {
5618 if (className) {
5619 changed = true;
5620 data[className] = value;
5621 }
5622 });
5623 }
5624 return changed;
5625 }
5626
5627 function handleCSSClassChanges() {
5628 forEach(postDigestElements, function(element) {
5629 var data = postDigestQueue.get(element);
5630 if (data) {
5631 var existing = splitClasses(element.attr('class'));
5632 var toAdd = '';
5633 var toRemove = '';
5634 forEach(data, function(status, className) {
5635 var hasClass = !!existing[className];
5636 if (status !== hasClass) {
5637 if (status) {
5638 toAdd += (toAdd.length ? ' ' : '') + className;
5639 } else {
5640 toRemove += (toRemove.length ? ' ' : '') + className;
5641 }
5642 }
5643 });
5644
5645 forEach(element, function(elm) {
5646 if (toAdd) {
5647 jqLiteAddClass(elm, toAdd);
5648 }
5649 if (toRemove) {
5650 jqLiteRemoveClass(elm, toRemove);
5651 }
5652 });
5653 postDigestQueue.delete(element);
5654 }
5655 });
5656 postDigestElements.length = 0;
5657 }
5658
5659
5660 function addRemoveClassesPostDigest(element, add, remove) {
5661 var data = postDigestQueue.get(element) || {};
5662
5663 var classesAdded = updateData(data, add, true);
5664 var classesRemoved = updateData(data, remove, false);
5665
5666 if (classesAdded || classesRemoved) {
5667
5668 postDigestQueue.set(element, data);
5669 postDigestElements.push(element);
5670
5671 if (postDigestElements.length === 1) {
5672 $rootScope.$$postDigest(handleCSSClassChanges);
5673 }
5674 }
5675 }
5676 }];
5677};
5678
5679/**
5680 * @ngdoc provider
5681 * @name $animateProvider
5682 *
5683 * @description
5684 * Default implementation of $animate that doesn't perform any animations, instead just
5685 * synchronously performs DOM updates and resolves the returned runner promise.
5686 *
5687 * In order to enable animations the `ngAnimate` module has to be loaded.
5688 *
5689 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5690 */
5691var $AnimateProvider = ['$provide', /** @this */ function($provide) {
5692 var provider = this;
5693 var classNameFilter = null;
5694 var customFilter = null;
5695
5696 this.$$registeredAnimations = Object.create(null);
5697
5698 /**
5699 * @ngdoc method
5700 * @name $animateProvider#register
5701 *
5702 * @description
5703 * Registers a new injectable animation factory function. The factory function produces the
5704 * animation object which contains callback functions for each event that is expected to be
5705 * animated.
5706 *
5707 * * `eventFn`: `function(element, ... , doneFunction, options)`
5708 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5709 * on the type of animation additional arguments will be injected into the animation function. The
5710 * list below explains the function signatures for the different animation methods:
5711 *
5712 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5713 * - addClass: function(element, addedClasses, doneFunction, options)
5714 * - removeClass: function(element, removedClasses, doneFunction, options)
5715 * - enter, leave, move: function(element, doneFunction, options)
5716 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5717 *
5718 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5719 *
5720 * ```js
5721 * return {
5722 * //enter, leave, move signature
5723 * eventFn : function(element, done, options) {
5724 * //code to run the animation
5725 * //once complete, then run done()
5726 * return function endFunction(wasCancelled) {
5727 * //code to cancel the animation
5728 * }
5729 * }
5730 * }
5731 * ```
5732 *
5733 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5734 * @param {Function} factory The factory function that will be executed to return the animation
5735 * object.
5736 */
5737 this.register = function(name, factory) {
5738 if (name && name.charAt(0) !== '.') {
5739 throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name);
5740 }
5741
5742 var key = name + '-animation';
5743 provider.$$registeredAnimations[name.substr(1)] = key;
5744 $provide.factory(key, factory);
5745 };
5746
5747 /**
5748 * @ngdoc method
5749 * @name $animateProvider#customFilter
5750 *
5751 * @description
5752 * Sets and/or returns the custom filter function that is used to "filter" animations, i.e.
5753 * determine if an animation is allowed or not. When no filter is specified (the default), no
5754 * animation will be blocked. Setting the `customFilter` value will only allow animations for
5755 * which the filter function's return value is truthy.
5756 *
5757 * This allows to easily create arbitrarily complex rules for filtering animations, such as
5758 * allowing specific events only, or enabling animations on specific subtrees of the DOM, etc.
5759 * Filtering animations can also boost performance for low-powered devices, as well as
5760 * applications containing a lot of structural operations.
5761 *
5762 * <div class="alert alert-success">
5763 * **Best Practice:**
5764 * Keep the filtering function as lean as possible, because it will be called for each DOM
5765 * action (e.g. insertion, removal, class change) performed by "animation-aware" directives.
5766 * See {@link guide/animations#which-directives-support-animations- here} for a list of built-in
5767 * directives that support animations.
5768 * Performing computationally expensive or time-consuming operations on each call of the
5769 * filtering function can make your animations sluggish.
5770 * </div>
5771 *
5772 * **Note:** If present, `customFilter` will be checked before
5773 * {@link $animateProvider#classNameFilter classNameFilter}.
5774 *
5775 * @param {Function=} filterFn - The filter function which will be used to filter all animations.
5776 * If a falsy value is returned, no animation will be performed. The function will be called
5777 * with the following arguments:
5778 * - **node** `{DOMElement}` - The DOM element to be animated.
5779 * - **event** `{String}` - The name of the animation event (e.g. `enter`, `leave`, `addClass`
5780 * etc).
5781 * - **options** `{Object}` - A collection of options/styles used for the animation.
5782 * @return {Function} The current filter function or `null` if there is none set.
5783 */
5784 this.customFilter = function(filterFn) {
5785 if (arguments.length === 1) {
5786 customFilter = isFunction(filterFn) ? filterFn : null;
5787 }
5788
5789 return customFilter;
5790 };
5791
5792 /**
5793 * @ngdoc method
5794 * @name $animateProvider#classNameFilter
5795 *
5796 * @description
5797 * Sets and/or returns the CSS class regular expression that is checked when performing
5798 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5799 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5800 * When setting the `classNameFilter` value, animations will only be performed on elements
5801 * that successfully match the filter expression. This in turn can boost performance
5802 * for low-powered devices as well as applications containing a lot of structural operations.
5803 *
5804 * **Note:** If present, `classNameFilter` will be checked after
5805 * {@link $animateProvider#customFilter customFilter}. If `customFilter` is present and returns
5806 * false, `classNameFilter` will not be checked.
5807 *
5808 * @param {RegExp=} expression The className expression which will be checked against all animations
5809 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5810 */
5811 this.classNameFilter = function(expression) {
5812 if (arguments.length === 1) {
5813 classNameFilter = (expression instanceof RegExp) ? expression : null;
5814 if (classNameFilter) {
5815 var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]');
5816 if (reservedRegex.test(classNameFilter.toString())) {
5817 classNameFilter = null;
5818 throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5819 }
5820 }
5821 }
5822 return classNameFilter;
5823 };
5824
5825 this.$get = ['$$animateQueue', function($$animateQueue) {
5826 function domInsert(element, parentElement, afterElement) {
5827 // if for some reason the previous element was removed
5828 // from the dom sometime before this code runs then let's
5829 // just stick to using the parent element as the anchor
5830 if (afterElement) {
5831 var afterNode = extractElementNode(afterElement);
5832 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5833 afterElement = null;
5834 }
5835 }
5836 if (afterElement) {
5837 afterElement.after(element);
5838 } else {
5839 parentElement.prepend(element);
5840 }
5841 }
5842
5843 /**
5844 * @ngdoc service
5845 * @name $animate
5846 * @description The $animate service exposes a series of DOM utility methods that provide support
5847 * for animation hooks. The default behavior is the application of DOM operations, however,
5848 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5849 * to ensure that animation runs with the triggered DOM operation.
5850 *
5851 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5852 * included and only when it is active then the animation hooks that `$animate` triggers will be
5853 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5854 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5855 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5856 *
5857 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5858 *
5859 * To learn more about enabling animation support, click here to visit the
5860 * {@link ngAnimate ngAnimate module page}.
5861 */
5862 return {
5863 // we don't call it directly since non-existant arguments may
5864 // be interpreted as null within the sub enabled function
5865
5866 /**
5867 *
5868 * @ngdoc method
5869 * @name $animate#on
5870 * @kind function
5871 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5872 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5873 * is fired with the following params:
5874 *
5875 * ```js
5876 * $animate.on('enter', container,
5877 * function callback(element, phase) {
5878 * // cool we detected an enter animation within the container
5879 * }
5880 * );
5881 * ```
5882 *
5883 * <div class="alert alert-warning">
5884 * **Note**: Generally, the events that are fired correspond 1:1 to `$animate` method names,
5885 * e.g. {@link ng.$animate#addClass addClass()} will fire `addClass`, and {@link ng.ngClass}
5886 * will fire `addClass` if classes are added, and `removeClass` if classes are removed.
5887 * However, there are two exceptions:
5888 *
5889 * <ul>
5890 * <li>if both an {@link ng.$animate#addClass addClass()} and a
5891 * {@link ng.$animate#removeClass removeClass()} action are performed during the same
5892 * animation, the event fired will be `setClass`. This is true even for `ngClass`.</li>
5893 * <li>an {@link ng.$animate#animate animate()} call that adds and removes classes will fire
5894 * the `setClass` event, but if it either removes or adds classes,
5895 * it will fire `animate` instead.</li>
5896 * </ul>
5897 *
5898 * </div>
5899 *
5900 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5901 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5902 * as well as among its children
5903 * @param {Function} callback the callback function that will be fired when the listener is triggered.
5904 *
5905 * The arguments present in the callback function are:
5906 * * `element` - The captured DOM element that the animation was fired on.
5907 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5908 * * `data` - an object with these properties:
5909 * * addClass - `{string|null}` - space-separated CSS classes to add to the element
5910 * * removeClass - `{string|null}` - space-separated CSS classes to remove from the element
5911 * * from - `{Object|null}` - CSS properties & values at the beginning of the animation
5912 * * to - `{Object|null}` - CSS properties & values at the end of the animation
5913 *
5914 * Note that the callback does not trigger a scope digest. Wrap your call into a
5915 * {@link $rootScope.Scope#$apply scope.$apply} to propagate changes to the scope.
5916 */
5917 on: $$animateQueue.on,
5918
5919 /**
5920 *
5921 * @ngdoc method
5922 * @name $animate#off
5923 * @kind function
5924 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5925 * can be used in three different ways depending on the arguments:
5926 *
5927 * ```js
5928 * // remove all the animation event listeners listening for `enter`
5929 * $animate.off('enter');
5930 *
5931 * // remove listeners for all animation events from the container element
5932 * $animate.off(container);
5933 *
5934 * // remove all the animation event listeners listening for `enter` on the given element and its children
5935 * $animate.off('enter', container);
5936 *
5937 * // remove the event listener function provided by `callback` that is set
5938 * // to listen for `enter` on the given `container` as well as its children
5939 * $animate.off('enter', container, callback);
5940 * ```
5941 *
5942 * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move,
5943 * addClass, removeClass, etc...), or the container element. If it is the element, all other
5944 * arguments are ignored.
5945 * @param {DOMElement=} container the container element the event listener was placed on
5946 * @param {Function=} callback the callback function that was registered as the listener
5947 */
5948 off: $$animateQueue.off,
5949
5950 /**
5951 * @ngdoc method
5952 * @name $animate#pin
5953 * @kind function
5954 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5955 * outside of the DOM structure of the AngularJS application. By doing so, any animation triggered via `$animate` can be issued on the
5956 * element despite being outside the realm of the application or within another application. Say for example if the application
5957 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5958 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5959 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5960 *
5961 * Note that this feature is only active when the `ngAnimate` module is used.
5962 *
5963 * @param {DOMElement} element the external element that will be pinned
5964 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5965 */
5966 pin: $$animateQueue.pin,
5967
5968 /**
5969 *
5970 * @ngdoc method
5971 * @name $animate#enabled
5972 * @kind function
5973 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5974 * function can be called in four ways:
5975 *
5976 * ```js
5977 * // returns true or false
5978 * $animate.enabled();
5979 *
5980 * // changes the enabled state for all animations
5981 * $animate.enabled(false);
5982 * $animate.enabled(true);
5983 *
5984 * // returns true or false if animations are enabled for an element
5985 * $animate.enabled(element);
5986 *
5987 * // changes the enabled state for an element and its children
5988 * $animate.enabled(element, true);
5989 * $animate.enabled(element, false);
5990 * ```
5991 *
5992 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5993 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5994 *
5995 * @return {boolean} whether or not animations are enabled
5996 */
5997 enabled: $$animateQueue.enabled,
5998
5999 /**
6000 * @ngdoc method
6001 * @name $animate#cancel
6002 * @kind function
6003 * @description Cancels the provided animation and applies the end state of the animation.
6004 * Note that this does not cancel the underlying operation, e.g. the setting of classes or
6005 * adding the element to the DOM.
6006 *
6007 * @param {animationRunner} animationRunner An animation runner returned by an $animate function.
6008 *
6009 * @example
6010 <example module="animationExample" deps="angular-animate.js" animations="true" name="animate-cancel">
6011 <file name="app.js">
6012 angular.module('animationExample', ['ngAnimate']).component('cancelExample', {
6013 templateUrl: 'template.html',
6014 controller: function($element, $animate) {
6015 this.runner = null;
6016
6017 this.addClass = function() {
6018 this.runner = $animate.addClass($element.find('div'), 'red');
6019 var ctrl = this;
6020 this.runner.finally(function() {
6021 ctrl.runner = null;
6022 });
6023 };
6024
6025 this.removeClass = function() {
6026 this.runner = $animate.removeClass($element.find('div'), 'red');
6027 var ctrl = this;
6028 this.runner.finally(function() {
6029 ctrl.runner = null;
6030 });
6031 };
6032
6033 this.cancel = function() {
6034 $animate.cancel(this.runner);
6035 };
6036 }
6037 });
6038 </file>
6039 <file name="template.html">
6040 <p>
6041 <button id="add" ng-click="$ctrl.addClass()">Add</button>
6042 <button ng-click="$ctrl.removeClass()">Remove</button>
6043 <br>
6044 <button id="cancel" ng-click="$ctrl.cancel()" ng-disabled="!$ctrl.runner">Cancel</button>
6045 <br>
6046 <div id="target">CSS-Animated Text</div>
6047 </p>
6048 </file>
6049 <file name="index.html">
6050 <cancel-example></cancel-example>
6051 </file>
6052 <file name="style.css">
6053 .red-add, .red-remove {
6054 transition: all 4s cubic-bezier(0.250, 0.460, 0.450, 0.940);
6055 }
6056
6057 .red,
6058 .red-add.red-add-active {
6059 color: #FF0000;
6060 font-size: 40px;
6061 }
6062
6063 .red-remove.red-remove-active {
6064 font-size: 10px;
6065 color: black;
6066 }
6067
6068 </file>
6069 </example>
6070 */
6071 cancel: function(runner) {
6072 if (runner.cancel) {
6073 runner.cancel();
6074 }
6075 },
6076
6077 /**
6078 *
6079 * @ngdoc method
6080 * @name $animate#enter
6081 * @kind function
6082 * @description Inserts the element into the DOM either after the `after` element (if provided) or
6083 * as the first child within the `parent` element and then triggers an animation.
6084 * A promise is returned that will be resolved during the next digest once the animation
6085 * has completed.
6086 *
6087 * @param {DOMElement} element the element which will be inserted into the DOM
6088 * @param {DOMElement} parent the parent element which will append the element as
6089 * a child (so long as the after element is not present)
6090 * @param {DOMElement=} after the sibling element after which the element will be appended
6091 * @param {object=} options an optional collection of options/styles that will be applied to the element.
6092 * The object can have the following properties:
6093 *
6094 * - **addClass** - `{string}` - space-separated CSS classes to add to element
6095 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6096 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
6097 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6098 *
6099 * @return {Runner} the animation runner
6100 */
6101 enter: function(element, parent, after, options) {
6102 parent = parent && jqLite(parent);
6103 after = after && jqLite(after);
6104 parent = parent || after.parent();
6105 domInsert(element, parent, after);
6106 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
6107 },
6108
6109 /**
6110 *
6111 * @ngdoc method
6112 * @name $animate#move
6113 * @kind function
6114 * @description Inserts (moves) the element into its new position in the DOM either after
6115 * the `after` element (if provided) or as the first child within the `parent` element
6116 * and then triggers an animation. A promise is returned that will be resolved
6117 * during the next digest once the animation has completed.
6118 *
6119 * @param {DOMElement} element the element which will be moved into the new DOM position
6120 * @param {DOMElement} parent the parent element which will append the element as
6121 * a child (so long as the after element is not present)
6122 * @param {DOMElement=} after the sibling element after which the element will be appended
6123 * @param {object=} options an optional collection of options/styles that will be applied to the element.
6124 * The object can have the following properties:
6125 *
6126 * - **addClass** - `{string}` - space-separated CSS classes to add to element
6127 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6128 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
6129 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6130 *
6131 * @return {Runner} the animation runner
6132 */
6133 move: function(element, parent, after, options) {
6134 parent = parent && jqLite(parent);
6135 after = after && jqLite(after);
6136 parent = parent || after.parent();
6137 domInsert(element, parent, after);
6138 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
6139 },
6140
6141 /**
6142 * @ngdoc method
6143 * @name $animate#leave
6144 * @kind function
6145 * @description Triggers an animation and then removes the element from the DOM.
6146 * When the function is called a promise is returned that will be resolved during the next
6147 * digest once the animation has completed.
6148 *
6149 * @param {DOMElement} element the element which will be removed from the DOM
6150 * @param {object=} options an optional collection of options/styles that will be applied to the element.
6151 * The object can have the following properties:
6152 *
6153 * - **addClass** - `{string}` - space-separated CSS classes to add to element
6154 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6155 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
6156 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6157 *
6158 * @return {Runner} the animation runner
6159 */
6160 leave: function(element, options) {
6161 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
6162 element.remove();
6163 });
6164 },
6165
6166 /**
6167 * @ngdoc method
6168 * @name $animate#addClass
6169 * @kind function
6170 *
6171 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
6172 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
6173 * animation if element already contains the CSS class or if the class is removed at a later step.
6174 * Note that class-based animations are treated differently compared to structural animations
6175 * (like enter, move and leave) since the CSS classes may be added/removed at different points
6176 * depending if CSS or JavaScript animations are used.
6177 *
6178 * @param {DOMElement} element the element which the CSS classes will be applied to
6179 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
6180 * @param {object=} options an optional collection of options/styles that will be applied to the element.
6181 * The object can have the following properties:
6182 *
6183 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
6184 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6185 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6186 *
6187 * @return {Runner} animationRunner the animation runner
6188 */
6189 addClass: function(element, className, options) {
6190 options = prepareAnimateOptions(options);
6191 options.addClass = mergeClasses(options.addclass, className);
6192 return $$animateQueue.push(element, 'addClass', options);
6193 },
6194
6195 /**
6196 * @ngdoc method
6197 * @name $animate#removeClass
6198 * @kind function
6199 *
6200 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
6201 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
6202 * animation if element does not contain the CSS class or if the class is added at a later step.
6203 * Note that class-based animations are treated differently compared to structural animations
6204 * (like enter, move and leave) since the CSS classes may be added/removed at different points
6205 * depending if CSS or JavaScript animations are used.
6206 *
6207 * @param {DOMElement} element the element which the CSS classes will be applied to
6208 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
6209 * @param {object=} options an optional collection of options/styles that will be applied to the element.
6210 * The object can have the following properties:
6211 *
6212 * - **addClass** - `{string}` - space-separated CSS classes to add to element
6213 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6214 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6215 *
6216 * @return {Runner} the animation runner
6217 */
6218 removeClass: function(element, className, options) {
6219 options = prepareAnimateOptions(options);
6220 options.removeClass = mergeClasses(options.removeClass, className);
6221 return $$animateQueue.push(element, 'removeClass', options);
6222 },
6223
6224 /**
6225 * @ngdoc method
6226 * @name $animate#setClass
6227 * @kind function
6228 *
6229 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
6230 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
6231 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
6232 * passed. Note that class-based animations are treated differently compared to structural animations
6233 * (like enter, move and leave) since the CSS classes may be added/removed at different points
6234 * depending if CSS or JavaScript animations are used.
6235 *
6236 * @param {DOMElement} element the element which the CSS classes will be applied to
6237 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
6238 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
6239 * @param {object=} options an optional collection of options/styles that will be applied to the element.
6240 * The object can have the following properties:
6241 *
6242 * - **addClass** - `{string}` - space-separated CSS classes to add to element
6243 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
6244 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6245 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6246 *
6247 * @return {Runner} the animation runner
6248 */
6249 setClass: function(element, add, remove, options) {
6250 options = prepareAnimateOptions(options);
6251 options.addClass = mergeClasses(options.addClass, add);
6252 options.removeClass = mergeClasses(options.removeClass, remove);
6253 return $$animateQueue.push(element, 'setClass', options);
6254 },
6255
6256 /**
6257 * @ngdoc method
6258 * @name $animate#animate
6259 * @kind function
6260 *
6261 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
6262 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
6263 * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and
6264 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
6265 * style in `to`, the style in `from` is applied immediately, and no animation is run.
6266 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
6267 * method (or as part of the `options` parameter):
6268 *
6269 * ```js
6270 * ngModule.animation('.my-inline-animation', function() {
6271 * return {
6272 * animate : function(element, from, to, done, options) {
6273 * //animation
6274 * done();
6275 * }
6276 * }
6277 * });
6278 * ```
6279 *
6280 * @param {DOMElement} element the element which the CSS styles will be applied to
6281 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
6282 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
6283 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
6284 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
6285 * (Note that if no animation is detected then this value will not be applied to the element.)
6286 * @param {object=} options an optional collection of options/styles that will be applied to the element.
6287 * The object can have the following properties:
6288 *
6289 * - **addClass** - `{string}` - space-separated CSS classes to add to element
6290 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6291 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
6292 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6293 *
6294 * @return {Runner} the animation runner
6295 */
6296 animate: function(element, from, to, className, options) {
6297 options = prepareAnimateOptions(options);
6298 options.from = options.from ? extend(options.from, from) : from;
6299 options.to = options.to ? extend(options.to, to) : to;
6300
6301 className = className || 'ng-inline-animate';
6302 options.tempClasses = mergeClasses(options.tempClasses, className);
6303 return $$animateQueue.push(element, 'animate', options);
6304 }
6305 };
6306 }];
6307}];
6308
6309var $$AnimateAsyncRunFactoryProvider = /** @this */ function() {
6310 this.$get = ['$$rAF', function($$rAF) {
6311 var waitQueue = [];
6312
6313 function waitForTick(fn) {
6314 waitQueue.push(fn);
6315 if (waitQueue.length > 1) return;
6316 $$rAF(function() {
6317 for (var i = 0; i < waitQueue.length; i++) {
6318 waitQueue[i]();
6319 }
6320 waitQueue = [];
6321 });
6322 }
6323
6324 return function() {
6325 var passed = false;
6326 waitForTick(function() {
6327 passed = true;
6328 });
6329 return function(callback) {
6330 if (passed) {
6331 callback();
6332 } else {
6333 waitForTick(callback);
6334 }
6335 };
6336 };
6337 }];
6338};
6339
6340var $$AnimateRunnerFactoryProvider = /** @this */ function() {
6341 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout',
6342 function($q, $sniffer, $$animateAsyncRun, $$isDocumentHidden, $timeout) {
6343
6344 var INITIAL_STATE = 0;
6345 var DONE_PENDING_STATE = 1;
6346 var DONE_COMPLETE_STATE = 2;
6347
6348 AnimateRunner.chain = function(chain, callback) {
6349 var index = 0;
6350
6351 next();
6352 function next() {
6353 if (index === chain.length) {
6354 callback(true);
6355 return;
6356 }
6357
6358 chain[index](function(response) {
6359 if (response === false) {
6360 callback(false);
6361 return;
6362 }
6363 index++;
6364 next();
6365 });
6366 }
6367 };
6368
6369 AnimateRunner.all = function(runners, callback) {
6370 var count = 0;
6371 var status = true;
6372 forEach(runners, function(runner) {
6373 runner.done(onProgress);
6374 });
6375
6376 function onProgress(response) {
6377 status = status && response;
6378 if (++count === runners.length) {
6379 callback(status);
6380 }
6381 }
6382 };
6383
6384 function AnimateRunner(host) {
6385 this.setHost(host);
6386
6387 var rafTick = $$animateAsyncRun();
6388 var timeoutTick = function(fn) {
6389 $timeout(fn, 0, false);
6390 };
6391
6392 this._doneCallbacks = [];
6393 this._tick = function(fn) {
6394 if ($$isDocumentHidden()) {
6395 timeoutTick(fn);
6396 } else {
6397 rafTick(fn);
6398 }
6399 };
6400 this._state = 0;
6401 }
6402
6403 AnimateRunner.prototype = {
6404 setHost: function(host) {
6405 this.host = host || {};
6406 },
6407
6408 done: function(fn) {
6409 if (this._state === DONE_COMPLETE_STATE) {
6410 fn();
6411 } else {
6412 this._doneCallbacks.push(fn);
6413 }
6414 },
6415
6416 progress: noop,
6417
6418 getPromise: function() {
6419 if (!this.promise) {
6420 var self = this;
6421 this.promise = $q(function(resolve, reject) {
6422 self.done(function(status) {
6423 if (status === false) {
6424 reject();
6425 } else {
6426 resolve();
6427 }
6428 });
6429 });
6430 }
6431 return this.promise;
6432 },
6433
6434 then: function(resolveHandler, rejectHandler) {
6435 return this.getPromise().then(resolveHandler, rejectHandler);
6436 },
6437
6438 'catch': function(handler) {
6439 return this.getPromise()['catch'](handler);
6440 },
6441
6442 'finally': function(handler) {
6443 return this.getPromise()['finally'](handler);
6444 },
6445
6446 pause: function() {
6447 if (this.host.pause) {
6448 this.host.pause();
6449 }
6450 },
6451
6452 resume: function() {
6453 if (this.host.resume) {
6454 this.host.resume();
6455 }
6456 },
6457
6458 end: function() {
6459 if (this.host.end) {
6460 this.host.end();
6461 }
6462 this._resolve(true);
6463 },
6464
6465 cancel: function() {
6466 if (this.host.cancel) {
6467 this.host.cancel();
6468 }
6469 this._resolve(false);
6470 },
6471
6472 complete: function(response) {
6473 var self = this;
6474 if (self._state === INITIAL_STATE) {
6475 self._state = DONE_PENDING_STATE;
6476 self._tick(function() {
6477 self._resolve(response);
6478 });
6479 }
6480 },
6481
6482 _resolve: function(response) {
6483 if (this._state !== DONE_COMPLETE_STATE) {
6484 forEach(this._doneCallbacks, function(fn) {
6485 fn(response);
6486 });
6487 this._doneCallbacks.length = 0;
6488 this._state = DONE_COMPLETE_STATE;
6489 }
6490 }
6491 };
6492
6493 return AnimateRunner;
6494 }];
6495};
6496
6497/* exported $CoreAnimateCssProvider */
6498
6499/**
6500 * @ngdoc service
6501 * @name $animateCss
6502 * @kind object
6503 * @this
6504 *
6505 * @description
6506 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
6507 * then the `$animateCss` service will actually perform animations.
6508 *
6509 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
6510 */
6511var $CoreAnimateCssProvider = function() {
6512 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {
6513
6514 return function(element, initialOptions) {
6515 // all of the animation functions should create
6516 // a copy of the options data, however, if a
6517 // parent service has already created a copy then
6518 // we should stick to using that
6519 var options = initialOptions || {};
6520 if (!options.$$prepared) {
6521 options = copy(options);
6522 }
6523
6524 // there is no point in applying the styles since
6525 // there is no animation that goes on at all in
6526 // this version of $animateCss.
6527 if (options.cleanupStyles) {
6528 options.from = options.to = null;
6529 }
6530
6531 if (options.from) {
6532 element.css(options.from);
6533 options.from = null;
6534 }
6535
6536 var closed, runner = new $$AnimateRunner();
6537 return {
6538 start: run,
6539 end: run
6540 };
6541
6542 function run() {
6543 $$rAF(function() {
6544 applyAnimationContents();
6545 if (!closed) {
6546 runner.complete();
6547 }
6548 closed = true;
6549 });
6550 return runner;
6551 }
6552
6553 function applyAnimationContents() {
6554 if (options.addClass) {
6555 element.addClass(options.addClass);
6556 options.addClass = null;
6557 }
6558 if (options.removeClass) {
6559 element.removeClass(options.removeClass);
6560 options.removeClass = null;
6561 }
6562 if (options.to) {
6563 element.css(options.to);
6564 options.to = null;
6565 }
6566 }
6567 };
6568 }];
6569};
6570
6571/* global getHash: true, stripHash: false */
6572
6573function getHash(url) {
6574 var index = url.indexOf('#');
6575 return index === -1 ? '' : url.substr(index);
6576}
6577
6578function trimEmptyHash(url) {
6579 return url.replace(/#$/, '');
6580}
6581
6582/**
6583 * ! This is a private undocumented service !
6584 *
6585 * @name $browser
6586 * @requires $log
6587 * @description
6588 * This object has two goals:
6589 *
6590 * - hide all the global state in the browser caused by the window object
6591 * - abstract away all the browser specific features and inconsistencies
6592 *
6593 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
6594 * service, which can be used for convenient testing of the application without the interaction with
6595 * the real browser apis.
6596 */
6597/**
6598 * @param {object} window The global window object.
6599 * @param {object} document jQuery wrapped document.
6600 * @param {object} $log window.console or an object with the same interface.
6601 * @param {object} $sniffer $sniffer service
6602 */
6603function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
6604 var self = this,
6605 location = window.location,
6606 history = window.history,
6607 setTimeout = window.setTimeout,
6608 clearTimeout = window.clearTimeout,
6609 pendingDeferIds = {},
6610 taskTracker = $$taskTrackerFactory($log);
6611
6612 self.isMock = false;
6613
6614 //////////////////////////////////////////////////////////////
6615 // Task-tracking API
6616 //////////////////////////////////////////////////////////////
6617
6618 // TODO(vojta): remove this temporary api
6619 self.$$completeOutstandingRequest = taskTracker.completeTask;
6620 self.$$incOutstandingRequestCount = taskTracker.incTaskCount;
6621
6622 // TODO(vojta): prefix this method with $$ ?
6623 self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;
6624
6625 //////////////////////////////////////////////////////////////
6626 // URL API
6627 //////////////////////////////////////////////////////////////
6628
6629 var cachedState, lastHistoryState,
6630 lastBrowserUrl = location.href,
6631 baseElement = document.find('base'),
6632 pendingLocation = null,
6633 getCurrentState = !$sniffer.history ? noop : function getCurrentState() {
6634 try {
6635 return history.state;
6636 } catch (e) {
6637 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
6638 }
6639 };
6640
6641 cacheState();
6642
6643 /**
6644 * @name $browser#url
6645 *
6646 * @description
6647 * GETTER:
6648 * Without any argument, this method just returns current value of `location.href` (with a
6649 * trailing `#` stripped of if the hash is empty).
6650 *
6651 * SETTER:
6652 * With at least one argument, this method sets url to new value.
6653 * If html5 history api supported, `pushState`/`replaceState` is used, otherwise
6654 * `location.href`/`location.replace` is used.
6655 * Returns its own instance to allow chaining.
6656 *
6657 * NOTE: this api is intended for use only by the `$location` service. Please use the
6658 * {@link ng.$location $location service} to change url.
6659 *
6660 * @param {string} url New url (when used as setter)
6661 * @param {boolean=} replace Should new url replace current history record?
6662 * @param {object=} state State object to use with `pushState`/`replaceState`
6663 */
6664 self.url = function(url, replace, state) {
6665 // In modern browsers `history.state` is `null` by default; treating it separately
6666 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
6667 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
6668 if (isUndefined(state)) {
6669 state = null;
6670 }
6671
6672 // Android Browser BFCache causes location, history reference to become stale.
6673 if (location !== window.location) location = window.location;
6674 if (history !== window.history) history = window.history;
6675
6676 // setter
6677 if (url) {
6678 var sameState = lastHistoryState === state;
6679
6680 // Normalize the inputted URL
6681 url = urlResolve(url).href;
6682
6683 // Don't change anything if previous and current URLs and states match. This also prevents
6684 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
6685 // See https://github.com/angular/angular.js/commit/ffb2701
6686 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
6687 return self;
6688 }
6689 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
6690 lastBrowserUrl = url;
6691 lastHistoryState = state;
6692 // Don't use history API if only the hash changed
6693 // due to a bug in IE10/IE11 which leads
6694 // to not firing a `hashchange` nor `popstate` event
6695 // in some cases (see #9143).
6696 if ($sniffer.history && (!sameBase || !sameState)) {
6697 history[replace ? 'replaceState' : 'pushState'](state, '', url);
6698 cacheState();
6699 } else {
6700 if (!sameBase) {
6701 pendingLocation = url;
6702 }
6703 if (replace) {
6704 location.replace(url);
6705 } else if (!sameBase) {
6706 location.href = url;
6707 } else {
6708 location.hash = getHash(url);
6709 }
6710 if (location.href !== url) {
6711 pendingLocation = url;
6712 }
6713 }
6714 if (pendingLocation) {
6715 pendingLocation = url;
6716 }
6717 return self;
6718 // getter
6719 } else {
6720 // - pendingLocation is needed as browsers don't allow to read out
6721 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
6722 // https://openradar.appspot.com/22186109).
6723 return trimEmptyHash(pendingLocation || location.href);
6724 }
6725 };
6726
6727 /**
6728 * @name $browser#state
6729 *
6730 * @description
6731 * This method is a getter.
6732 *
6733 * Return history.state or null if history.state is undefined.
6734 *
6735 * @returns {object} state
6736 */
6737 self.state = function() {
6738 return cachedState;
6739 };
6740
6741 var urlChangeListeners = [],
6742 urlChangeInit = false;
6743
6744 function cacheStateAndFireUrlChange() {
6745 pendingLocation = null;
6746 fireStateOrUrlChange();
6747 }
6748
6749 // This variable should be used *only* inside the cacheState function.
6750 var lastCachedState = null;
6751 function cacheState() {
6752 // This should be the only place in $browser where `history.state` is read.
6753 cachedState = getCurrentState();
6754 cachedState = isUndefined(cachedState) ? null : cachedState;
6755
6756 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
6757 if (equals(cachedState, lastCachedState)) {
6758 cachedState = lastCachedState;
6759 }
6760
6761 lastCachedState = cachedState;
6762 lastHistoryState = cachedState;
6763 }
6764
6765 function fireStateOrUrlChange() {
6766 var prevLastHistoryState = lastHistoryState;
6767 cacheState();
6768
6769 if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) {
6770 return;
6771 }
6772
6773 lastBrowserUrl = self.url();
6774 lastHistoryState = cachedState;
6775 forEach(urlChangeListeners, function(listener) {
6776 listener(self.url(), cachedState);
6777 });
6778 }
6779
6780 /**
6781 * @name $browser#onUrlChange
6782 *
6783 * @description
6784 * Register callback function that will be called, when url changes.
6785 *
6786 * It's only called when the url is changed from outside of AngularJS:
6787 * - user types different url into address bar
6788 * - user clicks on history (forward/back) button
6789 * - user clicks on a link
6790 *
6791 * It's not called when url is changed by $browser.url() method
6792 *
6793 * The listener gets called with new url as parameter.
6794 *
6795 * NOTE: this api is intended for use only by the $location service. Please use the
6796 * {@link ng.$location $location service} to monitor url changes in AngularJS apps.
6797 *
6798 * @param {function(string)} listener Listener function to be called when url changes.
6799 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
6800 */
6801 self.onUrlChange = function(callback) {
6802 // TODO(vojta): refactor to use node's syntax for events
6803 if (!urlChangeInit) {
6804 // We listen on both (hashchange/popstate) when available, as some browsers don't
6805 // fire popstate when user changes the address bar and don't fire hashchange when url
6806 // changed by push/replaceState
6807
6808 // html5 history api - popstate event
6809 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
6810 // hashchange event
6811 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
6812
6813 urlChangeInit = true;
6814 }
6815
6816 urlChangeListeners.push(callback);
6817 return callback;
6818 };
6819
6820 /**
6821 * @private
6822 * Remove popstate and hashchange handler from window.
6823 *
6824 * NOTE: this api is intended for use only by $rootScope.
6825 */
6826 self.$$applicationDestroyed = function() {
6827 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
6828 };
6829
6830 /**
6831 * Checks whether the url has changed outside of AngularJS.
6832 * Needs to be exported to be able to check for changes that have been done in sync,
6833 * as hashchange/popstate events fire in async.
6834 */
6835 self.$$checkUrlChange = fireStateOrUrlChange;
6836
6837 //////////////////////////////////////////////////////////////
6838 // Misc API
6839 //////////////////////////////////////////////////////////////
6840
6841 /**
6842 * @name $browser#baseHref
6843 *
6844 * @description
6845 * Returns current <base href>
6846 * (always relative - without domain)
6847 *
6848 * @returns {string} The current base href
6849 */
6850 self.baseHref = function() {
6851 var href = baseElement.attr('href');
6852 return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : '';
6853 };
6854
6855 /**
6856 * @name $browser#defer
6857 * @param {function()} fn A function, who's execution should be deferred.
6858 * @param {number=} [delay=0] Number of milliseconds to defer the function execution.
6859 * @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred.
6860 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
6861 *
6862 * @description
6863 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
6864 *
6865 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
6866 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
6867 * via `$browser.defer.flush()`.
6868 *
6869 */
6870 self.defer = function(fn, delay, taskType) {
6871 var timeoutId;
6872
6873 delay = delay || 0;
6874 taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;
6875
6876 taskTracker.incTaskCount(taskType);
6877 timeoutId = setTimeout(function() {
6878 delete pendingDeferIds[timeoutId];
6879 taskTracker.completeTask(fn, taskType);
6880 }, delay);
6881 pendingDeferIds[timeoutId] = taskType;
6882
6883 return timeoutId;
6884 };
6885
6886
6887 /**
6888 * @name $browser#defer.cancel
6889 *
6890 * @description
6891 * Cancels a deferred task identified with `deferId`.
6892 *
6893 * @param {*} deferId Token returned by the `$browser.defer` function.
6894 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
6895 * canceled.
6896 */
6897 self.defer.cancel = function(deferId) {
6898 if (pendingDeferIds.hasOwnProperty(deferId)) {
6899 var taskType = pendingDeferIds[deferId];
6900 delete pendingDeferIds[deferId];
6901 clearTimeout(deferId);
6902 taskTracker.completeTask(noop, taskType);
6903 return true;
6904 }
6905 return false;
6906 };
6907
6908}
6909
6910/** @this */
6911function $BrowserProvider() {
6912 this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory',
6913 function($window, $log, $sniffer, $document, $$taskTrackerFactory) {
6914 return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory);
6915 }];
6916}
6917
6918/**
6919 * @ngdoc service
6920 * @name $cacheFactory
6921 * @this
6922 *
6923 * @description
6924 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
6925 * them.
6926 *
6927 * ```js
6928 *
6929 * var cache = $cacheFactory('cacheId');
6930 * expect($cacheFactory.get('cacheId')).toBe(cache);
6931 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
6932 *
6933 * cache.put("key", "value");
6934 * cache.put("another key", "another value");
6935 *
6936 * // We've specified no options on creation
6937 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
6938 *
6939 * ```
6940 *
6941 *
6942 * @param {string} cacheId Name or id of the newly created cache.
6943 * @param {object=} options Options object that specifies the cache behavior. Properties:
6944 *
6945 * - `{number=}` `capacity` — turns the cache into LRU cache.
6946 *
6947 * @returns {object} Newly created cache object with the following set of methods:
6948 *
6949 * - `{object}` `info()` — Returns id, size, and options of cache.
6950 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
6951 * it.
6952 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
6953 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
6954 * - `{void}` `removeAll()` — Removes all cached values.
6955 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
6956 *
6957 * @example
6958 <example module="cacheExampleApp" name="cache-factory">
6959 <file name="index.html">
6960 <div ng-controller="CacheController">
6961 <input ng-model="newCacheKey" placeholder="Key">
6962 <input ng-model="newCacheValue" placeholder="Value">
6963 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
6964
6965 <p ng-if="keys.length">Cached Values</p>
6966 <div ng-repeat="key in keys">
6967 <span ng-bind="key"></span>
6968 <span>: </span>
6969 <b ng-bind="cache.get(key)"></b>
6970 </div>
6971
6972 <p>Cache Info</p>
6973 <div ng-repeat="(key, value) in cache.info()">
6974 <span ng-bind="key"></span>
6975 <span>: </span>
6976 <b ng-bind="value"></b>
6977 </div>
6978 </div>
6979 </file>
6980 <file name="script.js">
6981 angular.module('cacheExampleApp', []).
6982 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6983 $scope.keys = [];
6984 $scope.cache = $cacheFactory('cacheId');
6985 $scope.put = function(key, value) {
6986 if (angular.isUndefined($scope.cache.get(key))) {
6987 $scope.keys.push(key);
6988 }
6989 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6990 };
6991 }]);
6992 </file>
6993 <file name="style.css">
6994 p {
6995 margin: 10px 0 3px;
6996 }
6997 </file>
6998 </example>
6999 */
7000function $CacheFactoryProvider() {
7001
7002 this.$get = function() {
7003 var caches = {};
7004
7005 function cacheFactory(cacheId, options) {
7006 if (cacheId in caches) {
7007 throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId);
7008 }
7009
7010 var size = 0,
7011 stats = extend({}, options, {id: cacheId}),
7012 data = createMap(),
7013 capacity = (options && options.capacity) || Number.MAX_VALUE,
7014 lruHash = createMap(),
7015 freshEnd = null,
7016 staleEnd = null;
7017
7018 /**
7019 * @ngdoc type
7020 * @name $cacheFactory.Cache
7021 *
7022 * @description
7023 * A cache object used to store and retrieve data, primarily used by
7024 * {@link $templateRequest $templateRequest} and the {@link ng.directive:script script}
7025 * directive to cache templates and other data.
7026 *
7027 * ```js
7028 * angular.module('superCache')
7029 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
7030 * return $cacheFactory('super-cache');
7031 * }]);
7032 * ```
7033 *
7034 * Example test:
7035 *
7036 * ```js
7037 * it('should behave like a cache', inject(function(superCache) {
7038 * superCache.put('key', 'value');
7039 * superCache.put('another key', 'another value');
7040 *
7041 * expect(superCache.info()).toEqual({
7042 * id: 'super-cache',
7043 * size: 2
7044 * });
7045 *
7046 * superCache.remove('another key');
7047 * expect(superCache.get('another key')).toBeUndefined();
7048 *
7049 * superCache.removeAll();
7050 * expect(superCache.info()).toEqual({
7051 * id: 'super-cache',
7052 * size: 0
7053 * });
7054 * }));
7055 * ```
7056 */
7057 return (caches[cacheId] = {
7058
7059 /**
7060 * @ngdoc method
7061 * @name $cacheFactory.Cache#put
7062 * @kind function
7063 *
7064 * @description
7065 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
7066 * retrieved later, and incrementing the size of the cache if the key was not already
7067 * present in the cache. If behaving like an LRU cache, it will also remove stale
7068 * entries from the set.
7069 *
7070 * It will not insert undefined values into the cache.
7071 *
7072 * @param {string} key the key under which the cached data is stored.
7073 * @param {*} value the value to store alongside the key. If it is undefined, the key
7074 * will not be stored.
7075 * @returns {*} the value stored.
7076 */
7077 put: function(key, value) {
7078 if (isUndefined(value)) return;
7079 if (capacity < Number.MAX_VALUE) {
7080 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
7081
7082 refresh(lruEntry);
7083 }
7084
7085 if (!(key in data)) size++;
7086 data[key] = value;
7087
7088 if (size > capacity) {
7089 this.remove(staleEnd.key);
7090 }
7091
7092 return value;
7093 },
7094
7095 /**
7096 * @ngdoc method
7097 * @name $cacheFactory.Cache#get
7098 * @kind function
7099 *
7100 * @description
7101 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
7102 *
7103 * @param {string} key the key of the data to be retrieved
7104 * @returns {*} the value stored.
7105 */
7106 get: function(key) {
7107 if (capacity < Number.MAX_VALUE) {
7108 var lruEntry = lruHash[key];
7109
7110 if (!lruEntry) return;
7111
7112 refresh(lruEntry);
7113 }
7114
7115 return data[key];
7116 },
7117
7118
7119 /**
7120 * @ngdoc method
7121 * @name $cacheFactory.Cache#remove
7122 * @kind function
7123 *
7124 * @description
7125 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
7126 *
7127 * @param {string} key the key of the entry to be removed
7128 */
7129 remove: function(key) {
7130 if (capacity < Number.MAX_VALUE) {
7131 var lruEntry = lruHash[key];
7132
7133 if (!lruEntry) return;
7134
7135 if (lruEntry === freshEnd) freshEnd = lruEntry.p;
7136 if (lruEntry === staleEnd) staleEnd = lruEntry.n;
7137 link(lruEntry.n,lruEntry.p);
7138
7139 delete lruHash[key];
7140 }
7141
7142 if (!(key in data)) return;
7143
7144 delete data[key];
7145 size--;
7146 },
7147
7148
7149 /**
7150 * @ngdoc method
7151 * @name $cacheFactory.Cache#removeAll
7152 * @kind function
7153 *
7154 * @description
7155 * Clears the cache object of any entries.
7156 */
7157 removeAll: function() {
7158 data = createMap();
7159 size = 0;
7160 lruHash = createMap();
7161 freshEnd = staleEnd = null;
7162 },
7163
7164
7165 /**
7166 * @ngdoc method
7167 * @name $cacheFactory.Cache#destroy
7168 * @kind function
7169 *
7170 * @description
7171 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
7172 * removing it from the {@link $cacheFactory $cacheFactory} set.
7173 */
7174 destroy: function() {
7175 data = null;
7176 stats = null;
7177 lruHash = null;
7178 delete caches[cacheId];
7179 },
7180
7181
7182 /**
7183 * @ngdoc method
7184 * @name $cacheFactory.Cache#info
7185 * @kind function
7186 *
7187 * @description
7188 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
7189 *
7190 * @returns {object} an object with the following properties:
7191 * <ul>
7192 * <li>**id**: the id of the cache instance</li>
7193 * <li>**size**: the number of entries kept in the cache instance</li>
7194 * <li>**...**: any additional properties from the options object when creating the
7195 * cache.</li>
7196 * </ul>
7197 */
7198 info: function() {
7199 return extend({}, stats, {size: size});
7200 }
7201 });
7202
7203
7204 /**
7205 * makes the `entry` the freshEnd of the LRU linked list
7206 */
7207 function refresh(entry) {
7208 if (entry !== freshEnd) {
7209 if (!staleEnd) {
7210 staleEnd = entry;
7211 } else if (staleEnd === entry) {
7212 staleEnd = entry.n;
7213 }
7214
7215 link(entry.n, entry.p);
7216 link(entry, freshEnd);
7217 freshEnd = entry;
7218 freshEnd.n = null;
7219 }
7220 }
7221
7222
7223 /**
7224 * bidirectionally links two entries of the LRU linked list
7225 */
7226 function link(nextEntry, prevEntry) {
7227 if (nextEntry !== prevEntry) {
7228 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
7229 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
7230 }
7231 }
7232 }
7233
7234
7235 /**
7236 * @ngdoc method
7237 * @name $cacheFactory#info
7238 *
7239 * @description
7240 * Get information about all the caches that have been created
7241 *
7242 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
7243 */
7244 cacheFactory.info = function() {
7245 var info = {};
7246 forEach(caches, function(cache, cacheId) {
7247 info[cacheId] = cache.info();
7248 });
7249 return info;
7250 };
7251
7252
7253 /**
7254 * @ngdoc method
7255 * @name $cacheFactory#get
7256 *
7257 * @description
7258 * Get access to a cache object by the `cacheId` used when it was created.
7259 *
7260 * @param {string} cacheId Name or id of a cache to access.
7261 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
7262 */
7263 cacheFactory.get = function(cacheId) {
7264 return caches[cacheId];
7265 };
7266
7267
7268 return cacheFactory;
7269 };
7270}
7271
7272/**
7273 * @ngdoc service
7274 * @name $templateCache
7275 * @this
7276 *
7277 * @description
7278 * `$templateCache` is a {@link $cacheFactory.Cache Cache object} created by the
7279 * {@link ng.$cacheFactory $cacheFactory}.
7280 *
7281 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
7282 * can load templates directly into the cache in a `script` tag, by using {@link $templateRequest},
7283 * or by consuming the `$templateCache` service directly.
7284 *
7285 * Adding via the `script` tag:
7286 *
7287 * ```html
7288 * <script type="text/ng-template" id="templateId.html">
7289 * <p>This is the content of the template</p>
7290 * </script>
7291 * ```
7292 *
7293 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
7294 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (e.g.
7295 * element with {@link ngApp} attribute), otherwise the template will be ignored.
7296 *
7297 * Adding via the `$templateCache` service:
7298 *
7299 * ```js
7300 * var myApp = angular.module('myApp', []);
7301 * myApp.run(function($templateCache) {
7302 * $templateCache.put('templateId.html', 'This is the content of the template');
7303 * });
7304 * ```
7305 *
7306 * To retrieve the template later, simply use it in your component:
7307 * ```js
7308 * myApp.component('myComponent', {
7309 * templateUrl: 'templateId.html'
7310 * });
7311 * ```
7312 *
7313 * or get it via the `$templateCache` service:
7314 * ```js
7315 * $templateCache.get('templateId.html')
7316 * ```
7317 *
7318 */
7319function $TemplateCacheProvider() {
7320 this.$get = ['$cacheFactory', function($cacheFactory) {
7321 return $cacheFactory('templates');
7322 }];
7323}
7324
7325/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
7326 * Any commits to this file should be reviewed with security in mind. *
7327 * Changes to this file can potentially create security vulnerabilities. *
7328 * An approval from 2 Core members with history of modifying *
7329 * this file is required. *
7330 * *
7331 * Does the change somehow allow for arbitrary javascript to be executed? *
7332 * Or allows for someone to change the prototype of built-in objects? *
7333 * Or gives undesired access to variables like document or window? *
7334 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
7335
7336/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
7337 *
7338 * DOM-related variables:
7339 *
7340 * - "node" - DOM Node
7341 * - "element" - DOM Element or Node
7342 * - "$node" or "$element" - jqLite-wrapped node or element
7343 *
7344 *
7345 * Compiler related stuff:
7346 *
7347 * - "linkFn" - linking fn of a single directive
7348 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
7349 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
7350 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
7351 */
7352
7353
7354/**
7355 * @ngdoc service
7356 * @name $compile
7357 * @kind function
7358 *
7359 * @description
7360 * Compiles an HTML string or DOM into a template and produces a template function, which
7361 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
7362 *
7363 * The compilation is a process of walking the DOM tree and matching DOM elements to
7364 * {@link ng.$compileProvider#directive directives}.
7365 *
7366 * <div class="alert alert-warning">
7367 * **Note:** This document is an in-depth reference of all directive options.
7368 * For a gentle introduction to directives with examples of common use cases,
7369 * see the {@link guide/directive directive guide}.
7370 * </div>
7371 *
7372 * ## Comprehensive Directive API
7373 *
7374 * There are many different options for a directive.
7375 *
7376 * The difference resides in the return value of the factory function.
7377 * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)}
7378 * that defines the directive properties, or just the `postLink` function (all other properties will have
7379 * the default values).
7380 *
7381 * <div class="alert alert-success">
7382 * **Best Practice:** It's recommended to use the "directive definition object" form.
7383 * </div>
7384 *
7385 * Here's an example directive declared with a Directive Definition Object:
7386 *
7387 * ```js
7388 * var myModule = angular.module(...);
7389 *
7390 * myModule.directive('directiveName', function factory(injectables) {
7391 * var directiveDefinitionObject = {
7392 * {@link $compile#-priority- priority}: 0,
7393 * {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... },
7394 * // or
7395 * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... },
7396 * {@link $compile#-transclude- transclude}: false,
7397 * {@link $compile#-restrict- restrict}: 'A',
7398 * {@link $compile#-templatenamespace- templateNamespace}: 'html',
7399 * {@link $compile#-scope- scope}: false,
7400 * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
7401 * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier',
7402 * {@link $compile#-bindtocontroller- bindToController}: false,
7403 * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
7404 * {@link $compile#-multielement- multiElement}: false,
7405 * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) {
7406 * return {
7407 * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
7408 * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
7409 * }
7410 * // or
7411 * // return function postLink( ... ) { ... }
7412 * },
7413 * // or
7414 * // {@link $compile#-link- link}: {
7415 * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
7416 * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
7417 * // }
7418 * // or
7419 * // {@link $compile#-link- link}: function postLink( ... ) { ... }
7420 * };
7421 * return directiveDefinitionObject;
7422 * });
7423 * ```
7424 *
7425 * <div class="alert alert-warning">
7426 * **Note:** Any unspecified options will use the default value. You can see the default values below.
7427 * </div>
7428 *
7429 * Therefore the above can be simplified as:
7430 *
7431 * ```js
7432 * var myModule = angular.module(...);
7433 *
7434 * myModule.directive('directiveName', function factory(injectables) {
7435 * var directiveDefinitionObject = {
7436 * link: function postLink(scope, iElement, iAttrs) { ... }
7437 * };
7438 * return directiveDefinitionObject;
7439 * // or
7440 * // return function postLink(scope, iElement, iAttrs) { ... }
7441 * });
7442 * ```
7443 *
7444 * ### Life-cycle hooks
7445 * Directive controllers can provide the following methods that are called by AngularJS at points in the life-cycle of the
7446 * directive:
7447 * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
7448 * had their bindings initialized (and before the pre &amp; post linking functions for the directives on
7449 * this element). This is a good place to put initialization code for your controller.
7450 * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
7451 * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
7452 * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
7453 * component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will
7454 * also be called when your bindings are initialized.
7455 * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
7456 * changes. Any actions that you wish to take in response to the changes that you detect must be
7457 * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
7458 * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not
7459 * be detected by AngularJS's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments;
7460 * if detecting changes, you must store the previous value(s) for comparison to the current values.
7461 * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
7462 * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
7463 * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
7464 * components will have their `$onDestroy()` hook called before child components.
7465 * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
7466 * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
7467 * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
7468 * they are waiting for their template to load asynchronously and their own compilation and linking has been
7469 * suspended until that occurs.
7470 *
7471 * #### Comparison with life-cycle hooks in the new Angular
7472 * The new Angular also uses life-cycle hooks for its components. While the AngularJS life-cycle hooks are similar there are
7473 * some differences that you should be aware of, especially when it comes to moving your code from AngularJS to Angular:
7474 *
7475 * * AngularJS hooks are prefixed with `$`, such as `$onInit`. Angular hooks are prefixed with `ng`, such as `ngOnInit`.
7476 * * AngularJS hooks can be defined on the controller prototype or added to the controller inside its constructor.
7477 * In Angular you can only define hooks on the prototype of the Component class.
7478 * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in AngularJS than you would to
7479 * `ngDoCheck` in Angular.
7480 * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be
7481 * propagated throughout the application.
7482 * Angular does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an
7483 * error or do nothing depending upon the state of `enableProdMode()`.
7484 *
7485 * #### Life-cycle hook examples
7486 *
7487 * This example shows how you can check for mutations to a Date object even though the identity of the object
7488 * has not changed.
7489 *
7490 * <example name="doCheckDateExample" module="do-check-module">
7491 * <file name="app.js">
7492 * angular.module('do-check-module', [])
7493 * .component('app', {
7494 * template:
7495 * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' +
7496 * 'Date: {{ $ctrl.date }}' +
7497 * '<test date="$ctrl.date"></test>',
7498 * controller: function() {
7499 * this.date = new Date();
7500 * this.month = this.date.getMonth();
7501 * this.updateDate = function() {
7502 * this.date.setMonth(this.month);
7503 * };
7504 * }
7505 * })
7506 * .component('test', {
7507 * bindings: { date: '<' },
7508 * template:
7509 * '<pre>{{ $ctrl.log | json }}</pre>',
7510 * controller: function() {
7511 * var previousValue;
7512 * this.log = [];
7513 * this.$doCheck = function() {
7514 * var currentValue = this.date && this.date.valueOf();
7515 * if (previousValue !== currentValue) {
7516 * this.log.push('doCheck: date mutated: ' + this.date);
7517 * previousValue = currentValue;
7518 * }
7519 * };
7520 * }
7521 * });
7522 * </file>
7523 * <file name="index.html">
7524 * <app></app>
7525 * </file>
7526 * </example>
7527 *
7528 * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the
7529 * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large
7530 * arrays or objects can have a negative impact on your application performance.)
7531 *
7532 * <example name="doCheckArrayExample" module="do-check-module">
7533 * <file name="index.html">
7534 * <div ng-init="items = []">
7535 * <button ng-click="items.push(items.length)">Add Item</button>
7536 * <button ng-click="items = []">Reset Items</button>
7537 * <pre>{{ items }}</pre>
7538 * <test items="items"></test>
7539 * </div>
7540 * </file>
7541 * <file name="app.js">
7542 * angular.module('do-check-module', [])
7543 * .component('test', {
7544 * bindings: { items: '<' },
7545 * template:
7546 * '<pre>{{ $ctrl.log | json }}</pre>',
7547 * controller: function() {
7548 * this.log = [];
7549 *
7550 * this.$doCheck = function() {
7551 * if (this.items_ref !== this.items) {
7552 * this.log.push('doCheck: items changed');
7553 * this.items_ref = this.items;
7554 * }
7555 * if (!angular.equals(this.items_clone, this.items)) {
7556 * this.log.push('doCheck: items mutated');
7557 * this.items_clone = angular.copy(this.items);
7558 * }
7559 * };
7560 * }
7561 * });
7562 * </file>
7563 * </example>
7564 *
7565 *
7566 * ### Directive Definition Object
7567 *
7568 * The directive definition object provides instructions to the {@link ng.$compile
7569 * compiler}. The attributes are:
7570 *
7571 * #### `multiElement`
7572 * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between
7573 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
7574 * together as the directive elements. It is recommended that this feature be used on directives
7575 * which are not strictly behavioral (such as {@link ngClick}), and which
7576 * do not manipulate or replace child nodes (such as {@link ngInclude}).
7577 *
7578 * #### `priority`
7579 * When there are multiple directives defined on a single DOM element, sometimes it
7580 * is necessary to specify the order in which the directives are applied. The `priority` is used
7581 * to sort the directives before their `compile` functions get called. Priority is defined as a
7582 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
7583 * are also run in priority order, but post-link functions are run in reverse order. The order
7584 * of directives with the same priority is undefined. The default priority is `0`.
7585 *
7586 * #### `terminal`
7587 * If set to true then the current `priority` will be the last set of directives
7588 * which will execute (any directives at the current priority will still execute
7589 * as the order of execution on same `priority` is undefined). Note that expressions
7590 * and other directives used in the directive's template will also be excluded from execution.
7591 *
7592 * #### `scope`
7593 * The scope property can be `false`, `true`, or an object:
7594 *
7595 * * **`false` (default):** No scope will be created for the directive. The directive will use its
7596 * parent's scope.
7597 *
7598 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
7599 * the directive's element. If multiple directives on the same element request a new scope,
7600 * only one new scope is created.
7601 *
7602 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's template.
7603 * The 'isolate' scope differs from normal scope in that it does not prototypically
7604 * inherit from its parent scope. This is useful when creating reusable components, which should not
7605 * accidentally read or modify data in the parent scope. Note that an isolate scope
7606 * directive without a `template` or `templateUrl` will not apply the isolate scope
7607 * to its children elements.
7608 *
7609 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
7610 * directive's element. These local properties are useful for aliasing values for templates. The keys in
7611 * the object hash map to the name of the property on the isolate scope; the values define how the property
7612 * is bound to the parent scope, via matching attributes on the directive's element:
7613 *
7614 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
7615 * always a string since DOM attributes are strings. If no `attr` name is specified then the
7616 * attribute name is assumed to be the same as the local name. Given `<my-component
7617 * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`,
7618 * the directive's scope property `localName` will reflect the interpolated value of `hello
7619 * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's
7620 * scope. The `name` is read from the parent scope (not the directive's scope).
7621 *
7622 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression
7623 * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope.
7624 * If no `attr` name is specified then the attribute name is assumed to be the same as the local
7625 * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
7626 * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
7627 * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
7628 * `localModel` and vice versa. If the binding expression is non-assignable, or if the attribute
7629 * isn't optional and doesn't exist, an exception
7630 * ({@link error/$compile/nonassign `$compile:nonassign`}) will be thrown upon discovering changes
7631 * to the local value, since it will be impossible to sync them back to the parent scope.
7632 *
7633 * By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
7634 * method is used for tracking changes, and the equality check is based on object identity.
7635 * However, if an object literal or an array literal is passed as the binding expression, the
7636 * equality check is done by value (using the {@link angular.equals} function). It's also possible
7637 * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
7638 * `$watchCollection`}: use `=*` or `=*attr`
7639 *
7640 * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
7641 * expression passed via the attribute `attr`. The expression is evaluated in the context of the
7642 * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
7643 * local name.
7644 *
7645 * For example, given `<my-component my-attr="parentModel">` and directive definition of
7646 * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
7647 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
7648 * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
7649 * two caveats:
7650 * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply
7651 * sets the same value. That means if your bound value is an object, changes to its properties
7652 * in the isolated scope will be reflected in the parent scope (because both reference the same object).
7653 * 2. one-way binding watches changes to the **identity** of the parent value. That means the
7654 * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
7655 * to the value has changed. In most cases, this should not be of concern, but can be important
7656 * to know if you one-way bind to an object, and then replace that object in the isolated scope.
7657 * If you now change a property of the object in your parent scope, the change will not be
7658 * propagated to the isolated scope, because the identity of the object on the parent scope
7659 * has not changed. Instead you must assign a new object.
7660 *
7661 * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
7662 * back to the parent. However, it does not make this completely impossible.
7663 *
7664 * By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
7665 * method is used for tracking changes, and the equality check is based on object identity.
7666 * It's also possible to watch the evaluated value shallowly with
7667 * {@link ng.$rootScope.Scope#$watchCollection `$watchCollection`}: use `<*` or `<*attr`
7668 *
7669 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
7670 * no `attr` name is specified then the attribute name is assumed to be the same as the local name.
7671 * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
7672 * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for
7673 * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope
7674 * via an expression to the parent scope. This can be done by passing a map of local variable names
7675 * and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
7676 * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
7677 *
7678 * All 4 kinds of bindings (`@`, `=`, `<`, and `&`) can be made optional by adding `?` to the expression.
7679 * The marker must come after the mode and before the attribute name.
7680 * See the {@link error/$compile/iscp Invalid Isolate Scope Definition error} for definition examples.
7681 * This is useful to refine the interface directives provide.
7682 * One subtle difference between optional and non-optional happens **when the binding attribute is not
7683 * set**:
7684 * - the binding is optional: the property will not be defined
7685 * - the binding is not optional: the property is defined
7686 *
7687 * ```js
7688 *app.directive('testDir', function() {
7689 return {
7690 scope: {
7691 notoptional: '=',
7692 optional: '=?',
7693 },
7694 bindToController: true,
7695 controller: function() {
7696 this.$onInit = function() {
7697 console.log(this.hasOwnProperty('notoptional')) // true
7698 console.log(this.hasOwnProperty('optional')) // false
7699 }
7700 }
7701 }
7702 })
7703 *```
7704 *
7705 *
7706 * ##### Combining directives with different scope defintions
7707 *
7708 * In general it's possible to apply more than one directive to one element, but there might be limitations
7709 * depending on the type of scope required by the directives. The following points will help explain these limitations.
7710 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
7711 *
7712 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
7713 * * **child scope** + **no scope** => Both directives will share one single child scope
7714 * * **child scope** + **child scope** => Both directives will share one single child scope
7715 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
7716 * its parent's scope
7717 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
7718 * be applied to the same element.
7719 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
7720 * cannot be applied to the same element.
7721 *
7722 *
7723 * #### `bindToController`
7724 * This property is used to bind scope properties directly to the controller. It can be either
7725 * `true` or an object hash with the same format as the `scope` property.
7726 *
7727 * When an isolate scope is used for a directive (see above), `bindToController: true` will
7728 * allow a component to have its properties bound to the controller, rather than to scope.
7729 *
7730 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller
7731 * properties. You can access these bindings once they have been initialized by providing a controller method called
7732 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings
7733 * initialized.
7734 *
7735 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
7736 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used
7737 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
7738 * scope (useful for component directives).
7739 *
7740 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`.
7741 *
7742 *
7743 * #### `controller`
7744 * Controller constructor function. The controller is instantiated before the
7745 * pre-linking phase and can be accessed by other directives (see
7746 * `require` attribute). This allows the directives to communicate with each other and augment
7747 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
7748 *
7749 * * `$scope` - Current scope associated with the element
7750 * * `$element` - Current element
7751 * * `$attrs` - Current attributes object for the element
7752 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
7753 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
7754 * * `scope`: (optional) override the scope.
7755 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
7756 * * `futureParentElement` (optional):
7757 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
7758 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
7759 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
7760 * and when the `cloneLinkingFn` is passed,
7761 * as those elements need to created and cloned in a special way when they are defined outside their
7762 * usual containers (e.g. like `<svg>`).
7763 * * See also the `directive.templateNamespace` property.
7764 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
7765 * then the default transclusion is provided.
7766 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
7767 * `true` if the specified slot contains content (i.e. one or more DOM nodes).
7768 *
7769 * #### `require`
7770 * Require another directive and inject its controller as the fourth argument to the linking function. The
7771 * `require` property can be a string, an array or an object:
7772 * * a **string** containing the name of the directive to pass to the linking function
7773 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the
7774 * linking function will be an array of controllers in the same order as the names in the `require` property
7775 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument
7776 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding
7777 * controllers.
7778 *
7779 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are
7780 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers
7781 * have been constructed but before `$onInit` is called.
7782 * If the name of the required controller is the same as the local name (the key), the name can be
7783 * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`.
7784 * See the {@link $compileProvider#component} helper for an example of how this can be used.
7785 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is
7786 * raised (unless no link function is specified and the required controllers are not being bound to the directive
7787 * controller, in which case error checking is skipped). The name can be prefixed with:
7788 *
7789 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
7790 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
7791 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
7792 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
7793 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
7794 * `null` to the `link` fn if not found.
7795 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
7796 * `null` to the `link` fn if not found.
7797 *
7798 *
7799 * #### `controllerAs`
7800 * Identifier name for a reference to the controller in the directive's scope.
7801 * This allows the controller to be referenced from the directive template. This is especially
7802 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
7803 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
7804 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
7805 *
7806 *
7807 * #### `restrict`
7808 * String of subset of `EACM` which restricts the directive to a specific directive
7809 * declaration style. If omitted, the defaults (elements and attributes) are used.
7810 *
7811 * * `E` - Element name (default): `<my-directive></my-directive>`
7812 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
7813 * * `C` - Class: `<div class="my-directive: exp;"></div>`
7814 * * `M` - Comment: `<!-- directive: my-directive exp -->`
7815 *
7816 *
7817 * #### `templateNamespace`
7818 * String representing the document type used by the markup in the template.
7819 * AngularJS needs this information as those elements need to be created and cloned
7820 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
7821 *
7822 * * `html` - All root nodes in the template are HTML. Root nodes may also be
7823 * top-level elements such as `<svg>` or `<math>`.
7824 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
7825 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
7826 *
7827 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
7828 *
7829 * #### `template`
7830 * HTML markup that may:
7831 * * Replace the contents of the directive's element (default).
7832 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
7833 * * Wrap the contents of the directive's element (if `transclude` is true).
7834 *
7835 * Value may be:
7836 *
7837 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
7838 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
7839 * function api below) and returns a string value.
7840 *
7841 *
7842 * #### `templateUrl`
7843 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
7844 *
7845 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
7846 * for later when the template has been resolved. In the meantime it will continue to compile and link
7847 * sibling and parent elements as though this element had not contained any directives.
7848 *
7849 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
7850 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
7851 * case when only one deeply nested directive has `templateUrl`.
7852 *
7853 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}.
7854 *
7855 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
7856 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
7857 * a string value representing the url. In either case, the template URL is passed through {@link
7858 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
7859 *
7860 *
7861 * #### `replace`
7862 * <div class="alert alert-danger">
7863 * **Note:** `replace` is deprecated in AngularJS and has been removed in the new Angular (v2+).
7864 * </div>
7865 *
7866 * Specifies what the template should replace. Defaults to `false`.
7867 *
7868 * * `true` - the template will replace the directive's element.
7869 * * `false` - the template will replace the contents of the directive's element.
7870 *
7871 * The replacement process migrates all of the attributes / classes from the old element to the new
7872 * one. See the {@link guide/directive#template-expanding-directive
7873 * Directives Guide} for an example.
7874 *
7875 * There are very few scenarios where element replacement is required for the application function,
7876 * the main one being reusable custom components that are used within SVG contexts
7877 * (because SVG doesn't work with custom elements in the DOM tree).
7878 *
7879 * #### `transclude`
7880 * Extract the contents of the element where the directive appears and make it available to the directive.
7881 * The contents are compiled and provided to the directive as a **transclusion function**. See the
7882 * {@link $compile#transclusion Transclusion} section below.
7883 *
7884 *
7885 * #### `compile`
7886 *
7887 * ```js
7888 * function compile(tElement, tAttrs, transclude) { ... }
7889 * ```
7890 *
7891 * The compile function deals with transforming the template DOM. Since most directives do not do
7892 * template transformation, it is not used often. The compile function takes the following arguments:
7893 *
7894 * * `tElement` - template element - The element where the directive has been declared. It is
7895 * safe to do template transformation on the element and child elements only.
7896 *
7897 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
7898 * between all directive compile functions.
7899 *
7900 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
7901 *
7902 * <div class="alert alert-warning">
7903 * **Note:** The template instance and the link instance may be different objects if the template has
7904 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
7905 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
7906 * should be done in a linking function rather than in a compile function.
7907 * </div>
7908
7909 * <div class="alert alert-warning">
7910 * **Note:** The compile function cannot handle directives that recursively use themselves in their
7911 * own templates or compile functions. Compiling these directives results in an infinite loop and
7912 * stack overflow errors.
7913 *
7914 * This can be avoided by manually using `$compile` in the postLink function to imperatively compile
7915 * a directive's template instead of relying on automatic template compilation via `template` or
7916 * `templateUrl` declaration or manual compilation inside the compile function.
7917 * </div>
7918 *
7919 * <div class="alert alert-danger">
7920 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
7921 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
7922 * to the link function instead.
7923 * </div>
7924
7925 * A compile function can have a return value which can be either a function or an object.
7926 *
7927 * * returning a (post-link) function - is equivalent to registering the linking function via the
7928 * `link` property of the config object when the compile function is empty.
7929 *
7930 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
7931 * control when a linking function should be called during the linking phase. See info about
7932 * pre-linking and post-linking functions below.
7933 *
7934 *
7935 * #### `link`
7936 * This property is used only if the `compile` property is not defined.
7937 *
7938 * ```js
7939 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
7940 * ```
7941 *
7942 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
7943 * executed after the template has been cloned. This is where most of the directive logic will be
7944 * put.
7945 *
7946 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
7947 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
7948 *
7949 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
7950 * manipulate the children of the element only in `postLink` function since the children have
7951 * already been linked.
7952 *
7953 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
7954 * between all directive linking functions.
7955 *
7956 * * `controller` - the directive's required controller instance(s) - Instances are shared
7957 * among all directives, which allows the directives to use the controllers as a communication
7958 * channel. The exact value depends on the directive's `require` property:
7959 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
7960 * * `string`: the controller instance
7961 * * `array`: array of controller instances
7962 *
7963 * If a required controller cannot be found, and it is optional, the instance is `null`,
7964 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
7965 *
7966 * Note that you can also require the directive's own controller - it will be made available like
7967 * any other controller.
7968 *
7969 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
7970 * This is the same as the `$transclude` parameter of directive controllers,
7971 * see {@link ng.$compile#-controller- the controller section for details}.
7972 * `function([scope], cloneLinkingFn, futureParentElement)`.
7973 *
7974 * #### Pre-linking function
7975 *
7976 * Executed before the child elements are linked. Not safe to do DOM transformation since the
7977 * compiler linking function will fail to locate the correct elements for linking.
7978 *
7979 * #### Post-linking function
7980 *
7981 * Executed after the child elements are linked.
7982 *
7983 * Note that child elements that contain `templateUrl` directives will not have been compiled
7984 * and linked since they are waiting for their template to load asynchronously and their own
7985 * compilation and linking has been suspended until that occurs.
7986 *
7987 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
7988 * for their async templates to be resolved.
7989 *
7990 *
7991 * ### Transclusion
7992 *
7993 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
7994 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
7995 * scope from where they were taken.
7996 *
7997 * Transclusion is used (often with {@link ngTransclude}) to insert the
7998 * original contents of a directive's element into a specified place in the template of the directive.
7999 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
8000 * content has access to the properties on the scope from which it was taken, even if the directive
8001 * has isolated scope.
8002 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
8003 *
8004 * This makes it possible for the widget to have private state for its template, while the transcluded
8005 * content has access to its originating scope.
8006 *
8007 * <div class="alert alert-warning">
8008 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
8009 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
8010 * Testing Transclusion Directives}.
8011 * </div>
8012 *
8013 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
8014 * directive's element, the entire element or multiple parts of the element contents:
8015 *
8016 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
8017 * * `'element'` - transclude the whole of the directive's element including any directives on this
8018 * element that are defined at a lower priority than this directive. When used, the `template`
8019 * property is ignored.
8020 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
8021 *
8022 * **Multi-slot transclusion** is declared by providing an object for the `transclude` property.
8023 *
8024 * This object is a map where the keys are the name of the slot to fill and the value is an element selector
8025 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
8026 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
8027 *
8028 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}.
8029 *
8030 * If the element selector is prefixed with a `?` then that slot is optional.
8031 *
8032 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
8033 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
8034 *
8035 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
8036 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
8037 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
8038 * injectable into the directive's controller.
8039 *
8040 *
8041 * #### Transclusion Functions
8042 *
8043 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
8044 * function** to the directive's `link` function and `controller`. This transclusion function is a special
8045 * **linking function** that will return the compiled contents linked to a new transclusion scope.
8046 *
8047 * <div class="alert alert-info">
8048 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
8049 * ngTransclude will deal with it for us.
8050 * </div>
8051 *
8052 * If you want to manually control the insertion and removal of the transcluded content in your directive
8053 * then you must use this transclude function. When you call a transclude function it returns a jqLite/JQuery
8054 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
8055 *
8056 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
8057 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
8058 * content and the `scope` is the newly created transclusion scope, which the clone will be linked to.
8059 *
8060 * <div class="alert alert-info">
8061 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function
8062 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
8063 * </div>
8064 *
8065 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
8066 * attach function**:
8067 *
8068 * ```js
8069 * var transcludedContent, transclusionScope;
8070 *
8071 * $transclude(function(clone, scope) {
8072 * element.append(clone);
8073 * transcludedContent = clone;
8074 * transclusionScope = scope;
8075 * });
8076 * ```
8077 *
8078 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
8079 * associated transclusion scope:
8080 *
8081 * ```js
8082 * transcludedContent.remove();
8083 * transclusionScope.$destroy();
8084 * ```
8085 *
8086 * <div class="alert alert-info">
8087 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
8088 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
8089 * then you are also responsible for calling `$destroy` on the transclusion scope.
8090 * </div>
8091 *
8092 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
8093 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if
8094 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
8095 *
8096 *
8097 * #### Transclusion Scopes
8098 *
8099 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
8100 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
8101 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
8102 * was taken.
8103 *
8104 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
8105 * like this:
8106 *
8107 * ```html
8108 * <div ng-app>
8109 * <div isolate>
8110 * <div transclusion>
8111 * </div>
8112 * </div>
8113 * </div>
8114 * ```
8115 *
8116 * The `$parent` scope hierarchy will look like this:
8117 *
8118 ```
8119 - $rootScope
8120 - isolate
8121 - transclusion
8122 ```
8123 *
8124 * but the scopes will inherit prototypically from different scopes to their `$parent`.
8125 *
8126 ```
8127 - $rootScope
8128 - transclusion
8129 - isolate
8130 ```
8131 *
8132 *
8133 * ### Attributes
8134 *
8135 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
8136 * `link()` or `compile()` functions. It has a variety of uses.
8137 *
8138 * * *Accessing normalized attribute names:* Directives like `ngBind` can be expressed in many ways:
8139 * `ng:bind`, `data-ng-bind`, or `x-ng-bind`. The attributes object allows for normalized access
8140 * to the attributes.
8141 *
8142 * * *Directive inter-communication:* All directives share the same instance of the attributes
8143 * object which allows the directives to use the attributes object as inter directive
8144 * communication.
8145 *
8146 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
8147 * allowing other directives to read the interpolated value.
8148 *
8149 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
8150 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
8151 * the only way to easily get the actual value because during the linking phase the interpolation
8152 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
8153 *
8154 * ```js
8155 * function linkingFn(scope, elm, attrs, ctrl) {
8156 * // get the attribute value
8157 * console.log(attrs.ngModel);
8158 *
8159 * // change the attribute
8160 * attrs.$set('ngModel', 'new value');
8161 *
8162 * // observe changes to interpolated attribute
8163 * attrs.$observe('ngModel', function(value) {
8164 * console.log('ngModel has changed value to ' + value);
8165 * });
8166 * }
8167 * ```
8168 *
8169 * ## Example
8170 *
8171 * <div class="alert alert-warning">
8172 * **Note**: Typically directives are registered with `module.directive`. The example below is
8173 * to illustrate how `$compile` works.
8174 * </div>
8175 *
8176 <example module="compileExample" name="compile">
8177 <file name="index.html">
8178 <script>
8179 angular.module('compileExample', [], function($compileProvider) {
8180 // Configure new 'compile' directive by passing a directive
8181 // factory function. The factory function injects '$compile'.
8182 $compileProvider.directive('compile', function($compile) {
8183 // The directive factory creates a link function.
8184 return function(scope, element, attrs) {
8185 scope.$watch(
8186 function(scope) {
8187 // Watch the 'compile' expression for changes.
8188 return scope.$eval(attrs.compile);
8189 },
8190 function(value) {
8191 // When the 'compile' expression changes
8192 // assign it into the current DOM.
8193 element.html(value);
8194
8195 // Compile the new DOM and link it to the current scope.
8196 // NOTE: we only compile '.childNodes' so that we
8197 // don't get into an infinite loop compiling ourselves.
8198 $compile(element.contents())(scope);
8199 }
8200 );
8201 };
8202 });
8203 })
8204 .controller('GreeterController', ['$scope', function($scope) {
8205 $scope.name = 'AngularJS';
8206 $scope.html = 'Hello {{name}}';
8207 }]);
8208 </script>
8209 <div ng-controller="GreeterController">
8210 <input ng-model="name"> <br/>
8211 <textarea ng-model="html"></textarea> <br/>
8212 <div compile="html"></div>
8213 </div>
8214 </file>
8215 <file name="protractor.js" type="protractor">
8216 it('should auto compile', function() {
8217 var textarea = $('textarea');
8218 var output = $('div[compile]');
8219 // The initial state reads 'Hello AngularJS'.
8220 expect(output.getText()).toBe('Hello AngularJS');
8221 textarea.clear();
8222 textarea.sendKeys('{{name}}!');
8223 expect(output.getText()).toBe('AngularJS!');
8224 });
8225 </file>
8226 </example>
8227
8228 *
8229 *
8230 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
8231 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
8232 *
8233 * <div class="alert alert-danger">
8234 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
8235 * e.g. will not use the right outer scope. Please pass the transclude function as a
8236 * `parentBoundTranscludeFn` to the link function instead.
8237 * </div>
8238 *
8239 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
8240 * root element(s), not their children)
8241 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
8242 * (a DOM element/tree) to a scope. Where:
8243 *
8244 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
8245 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
8246 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
8247 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
8248 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
8249 *
8250 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
8251 * * `scope` - is the current scope with which the linking function is working with.
8252 *
8253 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
8254 * keys may be used to control linking behavior:
8255 *
8256 * * `parentBoundTranscludeFn` - the transclude function made available to
8257 * directives; if given, it will be passed through to the link functions of
8258 * directives found in `element` during compilation.
8259 * * `transcludeControllers` - an object hash with keys that map controller names
8260 * to a hash with the key `instance`, which maps to the controller instance;
8261 * if given, it will make the controllers available to directives on the compileNode:
8262 * ```
8263 * {
8264 * parent: {
8265 * instance: parentControllerInstance
8266 * }
8267 * }
8268 * ```
8269 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
8270 * the cloned elements; only needed for transcludes that are allowed to contain non HTML
8271 * elements (e.g. SVG elements). See also the `directive.controller` property.
8272 *
8273 * Calling the linking function returns the element of the template. It is either the original
8274 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
8275 *
8276 * After linking the view is not updated until after a call to `$digest`, which typically is done by
8277 * AngularJS automatically.
8278 *
8279 * If you need access to the bound view, there are two ways to do it:
8280 *
8281 * - If you are not asking the linking function to clone the template, create the DOM element(s)
8282 * before you send them to the compiler and keep this reference around.
8283 * ```js
8284 * var element = angular.element('<p>{{total}}</p>');
8285 * $compile(element)(scope);
8286 * ```
8287 *
8288 * - if on the other hand, you need the element to be cloned, the view reference from the original
8289 * example would not point to the clone, but rather to the original template that was cloned. In
8290 * this case, you can access the clone either via the `cloneAttachFn` or the value returned by the
8291 * linking function:
8292 * ```js
8293 * var templateElement = angular.element('<p>{{total}}</p>');
8294 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
8295 * // Attach the clone to DOM document at the right place.
8296 * });
8297 *
8298 * // Now we have reference to the cloned DOM via `clonedElement`.
8299 * // NOTE: The `clonedElement` returned by the linking function is the same as the
8300 * // `clonedElement` passed to `cloneAttachFn`.
8301 * ```
8302 *
8303 *
8304 * For information on how the compiler works, see the
8305 * {@link guide/compiler AngularJS HTML Compiler} section of the Developer Guide.
8306 *
8307 * @knownIssue
8308 *
8309 * ### Double Compilation
8310 *
8311 Double compilation occurs when an already compiled part of the DOM gets
8312 compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues,
8313 and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it
8314 section on double compilation} for an in-depth explanation and ways to avoid it.
8315
8316 * @knownIssue
8317
8318 ### Issues with `replace: true`
8319 *
8320 * <div class="alert alert-danger">
8321 * **Note**: {@link $compile#-replace- `replace: true`} is deprecated and not recommended to use,
8322 * mainly due to the issues listed here. It has been completely removed in the new Angular.
8323 * </div>
8324 *
8325 * #### Attribute values are not merged
8326 *
8327 * When a `replace` directive encounters the same attribute on the original and the replace node,
8328 * it will simply deduplicate the attribute and join the values with a space or with a `;` in case of
8329 * the `style` attribute.
8330 * ```html
8331 * Original Node: <span class="original" style="color: red;"></span>
8332 * Replace Template: <span class="replaced" style="background: blue;"></span>
8333 * Result: <span class="original replaced" style="color: red; background: blue;"></span>
8334 * ```
8335 *
8336 * That means attributes that contain AngularJS expressions will not be merged correctly, e.g.
8337 * {@link ngShow} or {@link ngClass} will cause a {@link $parse} error:
8338 *
8339 * ```html
8340 * Original Node: <span ng-class="{'something': something}" ng-show="!condition"></span>
8341 * Replace Template: <span ng-class="{'else': else}" ng-show="otherCondition"></span>
8342 * Result: <span ng-class="{'something': something} {'else': else}" ng-show="!condition otherCondition"></span>
8343 * ```
8344 *
8345 * See issue [#5695](https://github.com/angular/angular.js/issues/5695).
8346 *
8347 * #### Directives are not deduplicated before compilation
8348 *
8349 * When the original node and the replace template declare the same directive(s), they will be
8350 * {@link guide/compiler#double-compilation-and-how-to-avoid-it compiled twice} because the compiler
8351 * does not deduplicate them. In many cases, this is not noticeable, but e.g. {@link ngModel} will
8352 * attach `$formatters` and `$parsers` twice.
8353 *
8354 * See issue [#2573](https://github.com/angular/angular.js/issues/2573).
8355 *
8356 * #### `transclude: element` in the replace template root can have unexpected effects
8357 *
8358 * When the replace template has a directive at the root node that uses
8359 * {@link $compile#-transclude- `transclude: element`}, e.g.
8360 * {@link ngIf} or {@link ngRepeat}, the DOM structure or scope inheritance can be incorrect.
8361 * See the following issues:
8362 *
8363 * - Incorrect scope on replaced element:
8364 * [#9837](https://github.com/angular/angular.js/issues/9837)
8365 * - Different DOM between `template` and `templateUrl`:
8366 * [#10612](https://github.com/angular/angular.js/issues/14326)
8367 *
8368 */
8369
8370/**
8371 * @ngdoc directive
8372 * @name ngProp
8373 * @restrict A
8374 * @element ANY
8375 *
8376 * @usage
8377 *
8378 * ```html
8379 * <ANY ng-prop-propname="expression">
8380 * </ANY>
8381 * ```
8382 *
8383 * or with uppercase letters in property (e.g. "propName"):
8384 *
8385 *
8386 * ```html
8387 * <ANY ng-prop-prop_name="expression">
8388 * </ANY>
8389 * ```
8390 *
8391 *
8392 * @description
8393 * The `ngProp` directive binds an expression to a DOM element property.
8394 * `ngProp` allows writing to arbitrary properties by including
8395 * the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to
8396 * the `value` property.
8397 *
8398 * Usually, it's not necessary to write to properties in AngularJS, as the built-in directives
8399 * handle the most common use cases (instead of the above example, you would use {@link ngValue}).
8400 *
8401 * However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements)
8402 * often use custom properties to hold data, and `ngProp` can be used to provide input to these
8403 * custom elements.
8404 *
8405 * ## Binding to camelCase properties
8406 *
8407 * Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped.
8408 * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
8409 * `innerHTML` must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an
8410 * example, and for binding HTML {@link ngBindHtml} should be used.
8411 *
8412 * ## Security
8413 *
8414 * Binding expressions to arbitrary properties poses a security risk, as properties like `innerHTML`
8415 * can insert potentially dangerous HTML into the application, e.g. script tags that execute
8416 * malicious code.
8417 * For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}.
8418 * This means vulnerable properties require their content to be "trusted", based on the
8419 * context of the property. For example, the `innerHTML` is in the `HTML` context, and the
8420 * `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to
8421 * this property are trusted as a `RESOURCE_URL`.
8422 *
8423 * This can be set explicitly by calling $sce.trustAs(type, value) on the value that is
8424 * trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for
8425 * each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al.
8426 *
8427 * In some cases you can also rely upon automatic sanitization of untrusted values - see below.
8428 *
8429 * Based on the context, other options may exist to mark a value as trusted / configure the behavior
8430 * of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use
8431 * the {@link $sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList()}
8432 * and {@link $sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList()}.
8433 *
8434 * {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}.
8435 *
8436 * ### HTML Sanitization
8437 *
8438 * By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the
8439 * content.
8440 * However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the
8441 * potentially dangerous HTML, e.g. strip non-trusted tags and attributes when binding to
8442 * `innerHTML`.
8443 *
8444 * @example
8445 * ### Binding to different contexts
8446 *
8447 * <example name="ngProp" module="exampleNgProp">
8448 * <file name="app.js">
8449 * angular.module('exampleNgProp', [])
8450 * .component('main', {
8451 * templateUrl: 'main.html',
8452 * controller: function($sce) {
8453 * this.safeContent = '<strong>Safe content</strong>';
8454 * this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
8455 * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
8456 * }
8457 * });
8458 * </file>
8459 * <file name="main.html">
8460 * <div>
8461 * <div class="prop-unit">
8462 * Binding to a property without security context:
8463 * <div class="prop-binding" ng-prop-inner_text="$ctrl.safeContent"></div>
8464 * <span class="prop-note">innerText</span> (safeContent)
8465 * </div>
8466 *
8467 * <div class="prop-unit">
8468 * "Safe" content that requires a security context will throw because the contents could potentially be dangerous ...
8469 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
8470 * <span class="prop-note">innerHTML</span> (safeContent)
8471 * </div>
8472 *
8473 * <div class="prop-unit">
8474 * ... so that actually dangerous content cannot be executed:
8475 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
8476 * <span class="prop-note">innerHTML</span> (unsafeContent)
8477 * </div>
8478 *
8479 * <div class="prop-unit">
8480 * ... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure!
8481 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
8482 * <span class="prop-note">innerHTML</span> (trustedUnsafeContent)
8483 * </div>
8484 * </div>
8485 * </file>
8486 * <file name="index.html">
8487 * <main></main>
8488 * </file>
8489 * <file name="styles.css">
8490 * .prop-unit {
8491 * margin-bottom: 10px;
8492 * }
8493 *
8494 * .prop-binding {
8495 * min-height: 30px;
8496 * border: 1px solid blue;
8497 * }
8498 *
8499 * .prop-note {
8500 * font-family: Monospace;
8501 * }
8502 * </file>
8503 * </example>
8504 *
8505 *
8506 * @example
8507 * ### Binding to innerHTML with ngSanitize
8508 *
8509 * <example name="ngProp" module="exampleNgProp" deps="angular-sanitize.js">
8510 * <file name="app.js">
8511 * angular.module('exampleNgProp', ['ngSanitize'])
8512 * .component('main', {
8513 * templateUrl: 'main.html',
8514 * controller: function($sce) {
8515 * this.safeContent = '<strong>Safe content</strong>';
8516 * this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
8517 * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
8518 * }
8519 * });
8520 * </file>
8521 * <file name="main.html">
8522 * <div>
8523 * <div class="prop-unit">
8524 * "Safe" content will be sanitized ...
8525 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
8526 * <span class="prop-note">innerHTML</span> (safeContent)
8527 * </div>
8528 *
8529 * <div class="prop-unit">
8530 * ... as will dangerous content:
8531 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
8532 * <span class="prop-note">innerHTML</span> (unsafeContent)
8533 * </div>
8534 *
8535 * <div class="prop-unit">
8536 * ... and content that has been trusted explicitly works the same as without ngSanitize:
8537 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
8538 * <span class="prop-note">innerHTML</span> (trustedUnsafeContent)
8539 * </div>
8540 * </div>
8541 * </file>
8542 * <file name="index.html">
8543 * <main></main>
8544 * </file>
8545 * <file name="styles.css">
8546 * .prop-unit {
8547 * margin-bottom: 10px;
8548 * }
8549 *
8550 * .prop-binding {
8551 * min-height: 30px;
8552 * border: 1px solid blue;
8553 * }
8554 *
8555 * .prop-note {
8556 * font-family: Monospace;
8557 * }
8558 * </file>
8559 * </example>
8560 *
8561 */
8562
8563/** @ngdoc directive
8564 * @name ngOn
8565 * @restrict A
8566 * @element ANY
8567 *
8568 * @usage
8569 *
8570 * ```html
8571 * <ANY ng-on-eventname="expression">
8572 * </ANY>
8573 * ```
8574 *
8575 * or with uppercase letters in property (e.g. "eventName"):
8576 *
8577 *
8578 * ```html
8579 * <ANY ng-on-event_name="expression">
8580 * </ANY>
8581 * ```
8582 *
8583 * @description
8584 * The `ngOn` directive adds an event listener to a DOM element via
8585 * {@link angular.element angular.element().on()}, and evaluates an expression when the event is
8586 * fired.
8587 * `ngOn` allows adding listeners for arbitrary events by including
8588 * the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression
8589 * when the `drop` event is fired.
8590 *
8591 * AngularJS provides specific directives for many events, such as {@link ngClick}, so in most
8592 * cases it is not necessary to use `ngOn`. However, AngularJS does not support all events
8593 * (e.g. the `drop` event in the example above), and new events might be introduced in later DOM
8594 * standards.
8595 *
8596 * Another use-case for `ngOn` is listening to
8597 * [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events)
8598 * fired by
8599 * [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements).
8600 *
8601 * ## Binding to camelCase properties
8602 *
8603 * Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped.
8604 * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
8605 * `myEvent` must be written as `ng-on-my_event="expression"`.
8606 *
8607 * @example
8608 * ### Bind to built-in DOM events
8609 *
8610 * <example name="ngOn" module="exampleNgOn">
8611 * <file name="app.js">
8612 * angular.module('exampleNgOn', [])
8613 * .component('main', {
8614 * templateUrl: 'main.html',
8615 * controller: function() {
8616 * this.clickCount = 0;
8617 * this.mouseoverCount = 0;
8618 *
8619 * this.loadingState = 0;
8620 * }
8621 * });
8622 * </file>
8623 * <file name="main.html">
8624 * <div>
8625 * This is equivalent to `ngClick` and `ngMouseover`:<br>
8626 * <button
8627 * ng-on-click="$ctrl.clickCount = $ctrl.clickCount + 1"
8628 * ng-on-mouseover="$ctrl.mouseoverCount = $ctrl.mouseoverCount + 1">Click or mouseover</button><br>
8629 * clickCount: {{$ctrl.clickCount}}<br>
8630 * mouseover: {{$ctrl.mouseoverCount}}
8631 *
8632 * <hr>
8633 *
8634 * For the `error` and `load` event on images no built-in AngularJS directives exist:<br>
8635 * <img src="thisimagedoesnotexist.png" ng-on-error="$ctrl.loadingState = -1" ng-on-load="$ctrl.loadingState = 1"><br>
8636 * <div ng-switch="$ctrl.loadingState">
8637 * <span ng-switch-when="0">Image is loading</span>
8638 * <span ng-switch-when="-1">Image load error</span>
8639 * <span ng-switch-when="1">Image loaded successfully</span>
8640 * </div>
8641 * </div>
8642 * </file>
8643 * <file name="index.html">
8644 * <main></main>
8645 * </file>
8646 * </example>
8647 *
8648 *
8649 * @example
8650 * ### Bind to custom DOM events
8651 *
8652 * <example name="ngOnCustom" module="exampleNgOn">
8653 * <file name="app.js">
8654 * angular.module('exampleNgOn', [])
8655 * .component('main', {
8656 * templateUrl: 'main.html',
8657 * controller: function() {
8658 * this.eventLog = '';
8659 *
8660 * this.listener = function($event) {
8661 * this.eventLog = 'Event with type "' + $event.type + '" fired at ' + $event.detail;
8662 * };
8663 * }
8664 * })
8665 * .component('childComponent', {
8666 * templateUrl: 'child.html',
8667 * controller: function($element) {
8668 * this.fireEvent = function() {
8669 * var event = new CustomEvent('customtype', { detail: new Date()});
8670 *
8671 * $element[0].dispatchEvent(event);
8672 * };
8673 * }
8674 * });
8675 * </file>
8676 * <file name="main.html">
8677 * <child-component ng-on-customtype="$ctrl.listener($event)"></child-component><br>
8678 * <span>Event log: {{$ctrl.eventLog}}</span>
8679 * </file>
8680 * <file name="child.html">
8681 <button ng-click="$ctrl.fireEvent()">Fire custom event</button>
8682 * </file>
8683 * <file name="index.html">
8684 * <main></main>
8685 * </file>
8686 * </example>
8687 */
8688
8689var $compileMinErr = minErr('$compile');
8690
8691function UNINITIALIZED_VALUE() {}
8692var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();
8693
8694/**
8695 * @ngdoc provider
8696 * @name $compileProvider
8697 *
8698 * @description
8699 */
8700$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
8701/** @this */
8702function $CompileProvider($provide, $$sanitizeUriProvider) {
8703 var hasDirectives = {},
8704 Suffix = 'Directive',
8705 COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/,
8706 CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/,
8707 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
8708 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
8709
8710 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
8711 // The assumption is that future DOM event attribute names will begin with
8712 // 'on' and be composed of only English letters.
8713 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
8714 var bindingCache = createMap();
8715
8716 function parseIsolateBindings(scope, directiveName, isController) {
8717 var LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/;
8718
8719 var bindings = createMap();
8720
8721 forEach(scope, function(definition, scopeName) {
8722 definition = definition.trim();
8723
8724 if (definition in bindingCache) {
8725 bindings[scopeName] = bindingCache[definition];
8726 return;
8727 }
8728 var match = definition.match(LOCAL_REGEXP);
8729
8730 if (!match) {
8731 throw $compileMinErr('iscp',
8732 'Invalid {3} for directive \'{0}\'.' +
8733 ' Definition: {... {1}: \'{2}\' ...}',
8734 directiveName, scopeName, definition,
8735 (isController ? 'controller bindings definition' :
8736 'isolate scope definition'));
8737 }
8738
8739 bindings[scopeName] = {
8740 mode: match[1][0],
8741 collection: match[2] === '*',
8742 optional: match[3] === '?',
8743 attrName: match[4] || scopeName
8744 };
8745 if (match[4]) {
8746 bindingCache[definition] = bindings[scopeName];
8747 }
8748 });
8749
8750 return bindings;
8751 }
8752
8753 function parseDirectiveBindings(directive, directiveName) {
8754 var bindings = {
8755 isolateScope: null,
8756 bindToController: null
8757 };
8758 if (isObject(directive.scope)) {
8759 if (directive.bindToController === true) {
8760 bindings.bindToController = parseIsolateBindings(directive.scope,
8761 directiveName, true);
8762 bindings.isolateScope = {};
8763 } else {
8764 bindings.isolateScope = parseIsolateBindings(directive.scope,
8765 directiveName, false);
8766 }
8767 }
8768 if (isObject(directive.bindToController)) {
8769 bindings.bindToController =
8770 parseIsolateBindings(directive.bindToController, directiveName, true);
8771 }
8772 if (bindings.bindToController && !directive.controller) {
8773 // There is no controller
8774 throw $compileMinErr('noctrl',
8775 'Cannot bind to controller without directive \'{0}\'s controller.',
8776 directiveName);
8777 }
8778 return bindings;
8779 }
8780
8781 function assertValidDirectiveName(name) {
8782 var letter = name.charAt(0);
8783 if (!letter || letter !== lowercase(letter)) {
8784 throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name);
8785 }
8786 if (name !== name.trim()) {
8787 throw $compileMinErr('baddir',
8788 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces',
8789 name);
8790 }
8791 }
8792
8793 function getDirectiveRequire(directive) {
8794 var require = directive.require || (directive.controller && directive.name);
8795
8796 if (!isArray(require) && isObject(require)) {
8797 forEach(require, function(value, key) {
8798 var match = value.match(REQUIRE_PREFIX_REGEXP);
8799 var name = value.substring(match[0].length);
8800 if (!name) require[key] = match[0] + key;
8801 });
8802 }
8803
8804 return require;
8805 }
8806
8807 function getDirectiveRestrict(restrict, name) {
8808 if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) {
8809 throw $compileMinErr('badrestrict',
8810 'Restrict property \'{0}\' of directive \'{1}\' is invalid',
8811 restrict,
8812 name);
8813 }
8814
8815 return restrict || 'EA';
8816 }
8817
8818 /**
8819 * @ngdoc method
8820 * @name $compileProvider#directive
8821 * @kind function
8822 *
8823 * @description
8824 * Register a new directive with the compiler.
8825 *
8826 * @param {string|Object} name Name of the directive in camel-case (i.e. `ngBind` which will match
8827 * as `ng-bind`), or an object map of directives where the keys are the names and the values
8828 * are the factories.
8829 * @param {Function|Array} directiveFactory An injectable directive factory function. See the
8830 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
8831 * @returns {ng.$compileProvider} Self for chaining.
8832 */
8833 this.directive = function registerDirective(name, directiveFactory) {
8834 assertArg(name, 'name');
8835 assertNotHasOwnProperty(name, 'directive');
8836 if (isString(name)) {
8837 assertValidDirectiveName(name);
8838 assertArg(directiveFactory, 'directiveFactory');
8839 if (!hasDirectives.hasOwnProperty(name)) {
8840 hasDirectives[name] = [];
8841 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
8842 function($injector, $exceptionHandler) {
8843 var directives = [];
8844 forEach(hasDirectives[name], function(directiveFactory, index) {
8845 try {
8846 var directive = $injector.invoke(directiveFactory);
8847 if (isFunction(directive)) {
8848 directive = { compile: valueFn(directive) };
8849 } else if (!directive.compile && directive.link) {
8850 directive.compile = valueFn(directive.link);
8851 }
8852 directive.priority = directive.priority || 0;
8853 directive.index = index;
8854 directive.name = directive.name || name;
8855 directive.require = getDirectiveRequire(directive);
8856 directive.restrict = getDirectiveRestrict(directive.restrict, name);
8857 directive.$$moduleName = directiveFactory.$$moduleName;
8858 directives.push(directive);
8859 } catch (e) {
8860 $exceptionHandler(e);
8861 }
8862 });
8863 return directives;
8864 }]);
8865 }
8866 hasDirectives[name].push(directiveFactory);
8867 } else {
8868 forEach(name, reverseParams(registerDirective));
8869 }
8870 return this;
8871 };
8872
8873 /**
8874 * @ngdoc method
8875 * @name $compileProvider#component
8876 * @module ng
8877 * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
8878 * or an object map of components where the keys are the names and the values are the component definition objects.
8879 * @param {Object} options Component definition object (a simplified
8880 * {@link ng.$compile#directive-definition-object directive definition object}),
8881 * with the following properties (all optional):
8882 *
8883 * - `controller` – `{(string|function()=}` – controller constructor function that should be
8884 * associated with newly created scope or the name of a {@link ng.$compile#-controller-
8885 * registered controller} if passed as a string. An empty `noop` function by default.
8886 * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope.
8887 * If present, the controller will be published to scope under the `controllerAs` name.
8888 * If not present, this will default to be `$ctrl`.
8889 * - `template` – `{string=|function()=}` – html template as a string or a function that
8890 * returns an html template as a string which should be used as the contents of this component.
8891 * Empty string by default.
8892 *
8893 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with
8894 * the following locals:
8895 *
8896 * - `$element` - Current element
8897 * - `$attrs` - Current attributes object for the element
8898 *
8899 * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
8900 * template that should be used as the contents of this component.
8901 *
8902 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
8903 * the following locals:
8904 *
8905 * - `$element` - Current element
8906 * - `$attrs` - Current attributes object for the element
8907 *
8908 * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties.
8909 * Component properties are always bound to the component controller and not to the scope.
8910 * See {@link ng.$compile#-bindtocontroller- `bindToController`}.
8911 * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
8912 * Disabled by default.
8913 * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to
8914 * this component's controller. The object keys specify the property names under which the required
8915 * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}.
8916 * - `$...` – additional properties to attach to the directive factory function and the controller
8917 * constructor function. (This is used by the component router to annotate)
8918 *
8919 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
8920 * @description
8921 * Register a **component definition** with the compiler. This is a shorthand for registering a special
8922 * type of directive, which represents a self-contained UI component in your application. Such components
8923 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`).
8924 *
8925 * Component definitions are very simple and do not require as much configuration as defining general
8926 * directives. Component definitions usually consist only of a template and a controller backing it.
8927 *
8928 * In order to make the definition easier, components enforce best practices like use of `controllerAs`,
8929 * `bindToController`. They always have **isolate scope** and are restricted to elements.
8930 *
8931 * Here are a few examples of how you would usually define components:
8932 *
8933 * ```js
8934 * var myMod = angular.module(...);
8935 * myMod.component('myComp', {
8936 * template: '<div>My name is {{$ctrl.name}}</div>',
8937 * controller: function() {
8938 * this.name = 'shahar';
8939 * }
8940 * });
8941 *
8942 * myMod.component('myComp', {
8943 * template: '<div>My name is {{$ctrl.name}}</div>',
8944 * bindings: {name: '@'}
8945 * });
8946 *
8947 * myMod.component('myComp', {
8948 * templateUrl: 'views/my-comp.html',
8949 * controller: 'MyCtrl',
8950 * controllerAs: 'ctrl',
8951 * bindings: {name: '@'}
8952 * });
8953 *
8954 * ```
8955 * For more examples, and an in-depth guide, see the {@link guide/component component guide}.
8956 *
8957 * <br />
8958 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
8959 */
8960 this.component = function registerComponent(name, options) {
8961 if (!isString(name)) {
8962 forEach(name, reverseParams(bind(this, registerComponent)));
8963 return this;
8964 }
8965
8966 var controller = options.controller || function() {};
8967
8968 function factory($injector) {
8969 function makeInjectable(fn) {
8970 if (isFunction(fn) || isArray(fn)) {
8971 return /** @this */ function(tElement, tAttrs) {
8972 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
8973 };
8974 } else {
8975 return fn;
8976 }
8977 }
8978
8979 var template = (!options.template && !options.templateUrl ? '' : options.template);
8980 var ddo = {
8981 controller: controller,
8982 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
8983 template: makeInjectable(template),
8984 templateUrl: makeInjectable(options.templateUrl),
8985 transclude: options.transclude,
8986 scope: {},
8987 bindToController: options.bindings || {},
8988 restrict: 'E',
8989 require: options.require
8990 };
8991
8992 // Copy annotations (starting with $) over to the DDO
8993 forEach(options, function(val, key) {
8994 if (key.charAt(0) === '$') ddo[key] = val;
8995 });
8996
8997 return ddo;
8998 }
8999
9000 // TODO(pete) remove the following `forEach` before we release 1.6.0
9001 // The component-router@0.2.0 looks for the annotations on the controller constructor
9002 // Nothing in AngularJS looks for annotations on the factory function but we can't remove
9003 // it from 1.5.x yet.
9004
9005 // Copy any annotation properties (starting with $) over to the factory and controller constructor functions
9006 // These could be used by libraries such as the new component router
9007 forEach(options, function(val, key) {
9008 if (key.charAt(0) === '$') {
9009 factory[key] = val;
9010 // Don't try to copy over annotations to named controller
9011 if (isFunction(controller)) controller[key] = val;
9012 }
9013 });
9014
9015 factory.$inject = ['$injector'];
9016
9017 return this.directive(name, factory);
9018 };
9019
9020
9021 /**
9022 * @ngdoc method
9023 * @name $compileProvider#aHrefSanitizationTrustedUrlList
9024 * @kind function
9025 *
9026 * @description
9027 * Retrieves or overrides the default regular expression that is used for determining trusted safe
9028 * urls during a[href] sanitization.
9029 *
9030 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
9031 *
9032 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
9033 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationTrustedUrlList`
9034 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
9035 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
9036 *
9037 * @param {RegExp=} regexp New regexp to trust urls with.
9038 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
9039 * chaining otherwise.
9040 */
9041 this.aHrefSanitizationTrustedUrlList = function(regexp) {
9042 if (isDefined(regexp)) {
9043 $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(regexp);
9044 return this;
9045 } else {
9046 return $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList();
9047 }
9048 };
9049
9050
9051 /**
9052 * @ngdoc method
9053 * @name $compileProvider#aHrefSanitizationWhitelist
9054 * @kind function
9055 *
9056 * @deprecated
9057 * sinceVersion="1.8.1"
9058 *
9059 * This method is deprecated. Use {@link $compileProvider#aHrefSanitizationTrustedUrlList
9060 * aHrefSanitizationTrustedUrlList} instead.
9061 */
9062 Object.defineProperty(this, 'aHrefSanitizationWhitelist', {
9063 get: function() {
9064 return this.aHrefSanitizationTrustedUrlList;
9065 },
9066 set: function(value) {
9067 this.aHrefSanitizationTrustedUrlList = value;
9068 }
9069 });
9070
9071
9072 /**
9073 * @ngdoc method
9074 * @name $compileProvider#imgSrcSanitizationTrustedUrlList
9075 * @kind function
9076 *
9077 * @description
9078 * Retrieves or overrides the default regular expression that is used for determining trusted safe
9079 * urls during img[src] sanitization.
9080 *
9081 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
9082 *
9083 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
9084 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationTrustedUrlList`
9085 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
9086 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
9087 *
9088 * @param {RegExp=} regexp New regexp to trust urls with.
9089 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
9090 * chaining otherwise.
9091 */
9092 this.imgSrcSanitizationTrustedUrlList = function(regexp) {
9093 if (isDefined(regexp)) {
9094 $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(regexp);
9095 return this;
9096 } else {
9097 return $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList();
9098 }
9099 };
9100
9101
9102 /**
9103 * @ngdoc method
9104 * @name $compileProvider#imgSrcSanitizationWhitelist
9105 * @kind function
9106 *
9107 * @deprecated
9108 * sinceVersion="1.8.1"
9109 *
9110 * This method is deprecated. Use {@link $compileProvider#imgSrcSanitizationTrustedUrlList
9111 * imgSrcSanitizationTrustedUrlList} instead.
9112 */
9113 Object.defineProperty(this, 'imgSrcSanitizationWhitelist', {
9114 get: function() {
9115 return this.imgSrcSanitizationTrustedUrlList;
9116 },
9117 set: function(value) {
9118 this.imgSrcSanitizationTrustedUrlList = value;
9119 }
9120 });
9121
9122 /**
9123 * @ngdoc method
9124 * @name $compileProvider#debugInfoEnabled
9125 *
9126 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
9127 * current debugInfoEnabled state
9128 * @returns {*} current value if used as getter or itself (chaining) if used as setter
9129 *
9130 * @kind function
9131 *
9132 * @description
9133 * Call this method to enable/disable various debug runtime information in the compiler such as adding
9134 * binding information and a reference to the current scope on to DOM elements.
9135 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
9136 * * `ng-binding` CSS class
9137 * * `ng-scope` and `ng-isolated-scope` CSS classes
9138 * * `$binding` data property containing an array of the binding expressions
9139 * * Data properties used by the {@link angular.element#methods `scope()`/`isolateScope()` methods} to return
9140 * the element's scope.
9141 * * Placeholder comments will contain information about what directive and binding caused the placeholder.
9142 * E.g. `<!-- ngIf: shouldShow() -->`.
9143 *
9144 * You may want to disable this in production for a significant performance boost. See
9145 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
9146 *
9147 * The default value is true.
9148 */
9149 var debugInfoEnabled = true;
9150 this.debugInfoEnabled = function(enabled) {
9151 if (isDefined(enabled)) {
9152 debugInfoEnabled = enabled;
9153 return this;
9154 }
9155 return debugInfoEnabled;
9156 };
9157
9158 /**
9159 * @ngdoc method
9160 * @name $compileProvider#strictComponentBindingsEnabled
9161 *
9162 * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided,
9163 * otherwise return the current strictComponentBindingsEnabled state.
9164 * @returns {*} current value if used as getter or itself (chaining) if used as setter
9165 *
9166 * @kind function
9167 *
9168 * @description
9169 * Call this method to enable / disable the strict component bindings check. If enabled, the
9170 * compiler will enforce that all scope / controller bindings of a
9171 * {@link $compileProvider#directive directive} / {@link $compileProvider#component component}
9172 * that are not set as optional with `?`, must be provided when the directive is instantiated.
9173 * If not provided, the compiler will throw the
9174 * {@link error/$compile/missingattr $compile:missingattr error}.
9175 *
9176 * The default value is false.
9177 */
9178 var strictComponentBindingsEnabled = false;
9179 this.strictComponentBindingsEnabled = function(enabled) {
9180 if (isDefined(enabled)) {
9181 strictComponentBindingsEnabled = enabled;
9182 return this;
9183 }
9184 return strictComponentBindingsEnabled;
9185 };
9186
9187 var TTL = 10;
9188 /**
9189 * @ngdoc method
9190 * @name $compileProvider#onChangesTtl
9191 * @description
9192 *
9193 * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
9194 * assuming that the model is unstable.
9195 *
9196 * The current default is 10 iterations.
9197 *
9198 * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
9199 * in several iterations of calls to these hooks. However if an application needs more than the default 10
9200 * iterations to stabilize then you should investigate what is causing the model to continuously change during
9201 * the `$onChanges` hook execution.
9202 *
9203 * Increasing the TTL could have performance implications, so you should not change it without proper justification.
9204 *
9205 * @param {number} limit The number of `$onChanges` hook iterations.
9206 * @returns {number|object} the current limit (or `this` if called as a setter for chaining)
9207 */
9208 this.onChangesTtl = function(value) {
9209 if (arguments.length) {
9210 TTL = value;
9211 return this;
9212 }
9213 return TTL;
9214 };
9215
9216 var commentDirectivesEnabledConfig = true;
9217 /**
9218 * @ngdoc method
9219 * @name $compileProvider#commentDirectivesEnabled
9220 * @description
9221 *
9222 * It indicates to the compiler
9223 * whether or not directives on comments should be compiled.
9224 * Defaults to `true`.
9225 *
9226 * Calling this function with false disables the compilation of directives
9227 * on comments for the whole application.
9228 * This results in a compilation performance gain,
9229 * as the compiler doesn't have to check comments when looking for directives.
9230 * This should however only be used if you are sure that no comment directives are used in
9231 * the application (including any 3rd party directives).
9232 *
9233 * @param {boolean} enabled `false` if the compiler may ignore directives on comments
9234 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
9235 */
9236 this.commentDirectivesEnabled = function(value) {
9237 if (arguments.length) {
9238 commentDirectivesEnabledConfig = value;
9239 return this;
9240 }
9241 return commentDirectivesEnabledConfig;
9242 };
9243
9244
9245 var cssClassDirectivesEnabledConfig = true;
9246 /**
9247 * @ngdoc method
9248 * @name $compileProvider#cssClassDirectivesEnabled
9249 * @description
9250 *
9251 * It indicates to the compiler
9252 * whether or not directives on element classes should be compiled.
9253 * Defaults to `true`.
9254 *
9255 * Calling this function with false disables the compilation of directives
9256 * on element classes for the whole application.
9257 * This results in a compilation performance gain,
9258 * as the compiler doesn't have to check element classes when looking for directives.
9259 * This should however only be used if you are sure that no class directives are used in
9260 * the application (including any 3rd party directives).
9261 *
9262 * @param {boolean} enabled `false` if the compiler may ignore directives on element classes
9263 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
9264 */
9265 this.cssClassDirectivesEnabled = function(value) {
9266 if (arguments.length) {
9267 cssClassDirectivesEnabledConfig = value;
9268 return this;
9269 }
9270 return cssClassDirectivesEnabledConfig;
9271 };
9272
9273
9274 /**
9275 * The security context of DOM Properties.
9276 * @private
9277 */
9278 var PROP_CONTEXTS = createMap();
9279
9280 /**
9281 * @ngdoc method
9282 * @name $compileProvider#addPropertySecurityContext
9283 * @description
9284 *
9285 * Defines the security context for DOM properties bound by ng-prop-*.
9286 *
9287 * @param {string} elementName The element name or '*' to match any element.
9288 * @param {string} propertyName The DOM property name.
9289 * @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL`
9290 * @returns {object} `this` for chaining
9291 */
9292 this.addPropertySecurityContext = function(elementName, propertyName, ctx) {
9293 var key = (elementName.toLowerCase() + '|' + propertyName.toLowerCase());
9294
9295 if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) {
9296 throw $compileMinErr('ctxoverride', 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.', elementName, propertyName, PROP_CONTEXTS[key], ctx);
9297 }
9298
9299 PROP_CONTEXTS[key] = ctx;
9300 return this;
9301 };
9302
9303 /* Default property contexts.
9304 *
9305 * Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58
9306 * Changing:
9307 * - SecurityContext.* => SCE_CONTEXTS/$sce.*
9308 * - STYLE => CSS
9309 * - various URL => MEDIA_URL
9310 * - *|formAction, form|action URL => RESOURCE_URL (like the attribute)
9311 */
9312 (function registerNativePropertyContexts() {
9313 function registerContext(ctx, values) {
9314 forEach(values, function(v) { PROP_CONTEXTS[v.toLowerCase()] = ctx; });
9315 }
9316
9317 registerContext(SCE_CONTEXTS.HTML, [
9318 'iframe|srcdoc',
9319 '*|innerHTML',
9320 '*|outerHTML'
9321 ]);
9322 registerContext(SCE_CONTEXTS.CSS, ['*|style']);
9323 registerContext(SCE_CONTEXTS.URL, [
9324 'area|href', 'area|ping',
9325 'a|href', 'a|ping',
9326 'blockquote|cite',
9327 'body|background',
9328 'del|cite',
9329 'input|src',
9330 'ins|cite',
9331 'q|cite'
9332 ]);
9333 registerContext(SCE_CONTEXTS.MEDIA_URL, [
9334 'audio|src',
9335 'img|src', 'img|srcset',
9336 'source|src', 'source|srcset',
9337 'track|src',
9338 'video|src', 'video|poster'
9339 ]);
9340 registerContext(SCE_CONTEXTS.RESOURCE_URL, [
9341 '*|formAction',
9342 'applet|code', 'applet|codebase',
9343 'base|href',
9344 'embed|src',
9345 'frame|src',
9346 'form|action',
9347 'head|profile',
9348 'html|manifest',
9349 'iframe|src',
9350 'link|href',
9351 'media|src',
9352 'object|codebase', 'object|data',
9353 'script|src'
9354 ]);
9355 })();
9356
9357
9358 this.$get = [
9359 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
9360 '$controller', '$rootScope', '$sce', '$animate',
9361 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
9362 $controller, $rootScope, $sce, $animate) {
9363
9364 var SIMPLE_ATTR_NAME = /^\w/;
9365 var specialAttrHolder = window.document.createElement('div');
9366
9367
9368 var commentDirectivesEnabled = commentDirectivesEnabledConfig;
9369 var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig;
9370
9371
9372 var onChangesTtl = TTL;
9373 // The onChanges hooks should all be run together in a single digest
9374 // When changes occur, the call to trigger their hooks will be added to this queue
9375 var onChangesQueue;
9376
9377 // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
9378 function flushOnChangesQueue() {
9379 try {
9380 if (!(--onChangesTtl)) {
9381 // We have hit the TTL limit so reset everything
9382 onChangesQueue = undefined;
9383 throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL);
9384 }
9385 // We must run this hook in an apply since the $$postDigest runs outside apply
9386 $rootScope.$apply(function() {
9387 for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
9388 try {
9389 onChangesQueue[i]();
9390 } catch (e) {
9391 $exceptionHandler(e);
9392 }
9393 }
9394 // Reset the queue to trigger a new schedule next time there is a change
9395 onChangesQueue = undefined;
9396 });
9397 } finally {
9398 onChangesTtl++;
9399 }
9400 }
9401
9402
9403 function sanitizeSrcset(value, invokeType) {
9404 if (!value) {
9405 return value;
9406 }
9407 if (!isString(value)) {
9408 throw $compileMinErr('srcset', 'Can\'t pass trusted values to `{0}`: "{1}"', invokeType, value.toString());
9409 }
9410
9411 // Such values are a bit too complex to handle automatically inside $sce.
9412 // Instead, we sanitize each of the URIs individually, which works, even dynamically.
9413
9414 // It's not possible to work around this using `$sce.trustAsMediaUrl`.
9415 // If you want to programmatically set explicitly trusted unsafe URLs, you should use
9416 // `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
9417 // `ng-bind-html` directive.
9418
9419 var result = '';
9420
9421 // first check if there are spaces because it's not the same pattern
9422 var trimmedSrcset = trim(value);
9423 // ( 999x ,| 999w ,| ,|, )
9424 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
9425 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
9426
9427 // split srcset into tuple of uri and descriptor except for the last item
9428 var rawUris = trimmedSrcset.split(pattern);
9429
9430 // for each tuples
9431 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
9432 for (var i = 0; i < nbrUrisWith2parts; i++) {
9433 var innerIdx = i * 2;
9434 // sanitize the uri
9435 result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
9436 // add the descriptor
9437 result += ' ' + trim(rawUris[innerIdx + 1]);
9438 }
9439
9440 // split the last item into uri and descriptor
9441 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
9442
9443 // sanitize the last uri
9444 result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));
9445
9446 // and add the last descriptor if any
9447 if (lastTuple.length === 2) {
9448 result += (' ' + trim(lastTuple[1]));
9449 }
9450 return result;
9451 }
9452
9453
9454 function Attributes(element, attributesToCopy) {
9455 if (attributesToCopy) {
9456 var keys = Object.keys(attributesToCopy);
9457 var i, l, key;
9458
9459 for (i = 0, l = keys.length; i < l; i++) {
9460 key = keys[i];
9461 this[key] = attributesToCopy[key];
9462 }
9463 } else {
9464 this.$attr = {};
9465 }
9466
9467 this.$$element = element;
9468 }
9469
9470 Attributes.prototype = {
9471 /**
9472 * @ngdoc method
9473 * @name $compile.directive.Attributes#$normalize
9474 * @kind function
9475 *
9476 * @description
9477 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
9478 * `data-`) to its normalized, camelCase form.
9479 *
9480 * Also there is special case for Moz prefix starting with upper case letter.
9481 *
9482 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
9483 *
9484 * @param {string} name Name to normalize
9485 */
9486 $normalize: directiveNormalize,
9487
9488
9489 /**
9490 * @ngdoc method
9491 * @name $compile.directive.Attributes#$addClass
9492 * @kind function
9493 *
9494 * @description
9495 * Adds the CSS class value specified by the classVal parameter to the element. If animations
9496 * are enabled then an animation will be triggered for the class addition.
9497 *
9498 * @param {string} classVal The className value that will be added to the element
9499 */
9500 $addClass: function(classVal) {
9501 if (classVal && classVal.length > 0) {
9502 $animate.addClass(this.$$element, classVal);
9503 }
9504 },
9505
9506 /**
9507 * @ngdoc method
9508 * @name $compile.directive.Attributes#$removeClass
9509 * @kind function
9510 *
9511 * @description
9512 * Removes the CSS class value specified by the classVal parameter from the element. If
9513 * animations are enabled then an animation will be triggered for the class removal.
9514 *
9515 * @param {string} classVal The className value that will be removed from the element
9516 */
9517 $removeClass: function(classVal) {
9518 if (classVal && classVal.length > 0) {
9519 $animate.removeClass(this.$$element, classVal);
9520 }
9521 },
9522
9523 /**
9524 * @ngdoc method
9525 * @name $compile.directive.Attributes#$updateClass
9526 * @kind function
9527 *
9528 * @description
9529 * Adds and removes the appropriate CSS class values to the element based on the difference
9530 * between the new and old CSS class values (specified as newClasses and oldClasses).
9531 *
9532 * @param {string} newClasses The current CSS className value
9533 * @param {string} oldClasses The former CSS className value
9534 */
9535 $updateClass: function(newClasses, oldClasses) {
9536 var toAdd = tokenDifference(newClasses, oldClasses);
9537 if (toAdd && toAdd.length) {
9538 $animate.addClass(this.$$element, toAdd);
9539 }
9540
9541 var toRemove = tokenDifference(oldClasses, newClasses);
9542 if (toRemove && toRemove.length) {
9543 $animate.removeClass(this.$$element, toRemove);
9544 }
9545 },
9546
9547 /**
9548 * Set a normalized attribute on the element in a way such that all directives
9549 * can share the attribute. This function properly handles boolean attributes.
9550 * @param {string} key Normalized key. (ie ngAttribute)
9551 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
9552 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
9553 * Defaults to true.
9554 * @param {string=} attrName Optional none normalized name. Defaults to key.
9555 */
9556 $set: function(key, value, writeAttr, attrName) {
9557 // TODO: decide whether or not to throw an error if "class"
9558 // is set through this function since it may cause $updateClass to
9559 // become unstable.
9560
9561 var node = this.$$element[0],
9562 booleanKey = getBooleanAttrName(node, key),
9563 aliasedKey = getAliasedAttrName(key),
9564 observer = key,
9565 nodeName;
9566
9567 if (booleanKey) {
9568 this.$$element.prop(key, value);
9569 attrName = booleanKey;
9570 } else if (aliasedKey) {
9571 this[aliasedKey] = value;
9572 observer = aliasedKey;
9573 }
9574
9575 this[key] = value;
9576
9577 // translate normalized key to actual key
9578 if (attrName) {
9579 this.$attr[key] = attrName;
9580 } else {
9581 attrName = this.$attr[key];
9582 if (!attrName) {
9583 this.$attr[key] = attrName = snake_case(key, '-');
9584 }
9585 }
9586
9587 nodeName = nodeName_(this.$$element);
9588
9589 // Sanitize img[srcset] values.
9590 if (nodeName === 'img' && key === 'srcset') {
9591 this[key] = value = sanitizeSrcset(value, '$set(\'srcset\', value)');
9592 }
9593
9594 if (writeAttr !== false) {
9595 if (value === null || isUndefined(value)) {
9596 this.$$element.removeAttr(attrName);
9597 } else {
9598 if (SIMPLE_ATTR_NAME.test(attrName)) {
9599 // jQuery skips special boolean attrs treatment in XML nodes for
9600 // historical reasons and hence AngularJS cannot freely call
9601 // `.attr(attrName, false) with such attributes. To avoid issues
9602 // in XHTML, call `removeAttr` in such cases instead.
9603 // See https://github.com/jquery/jquery/issues/4249
9604 if (booleanKey && value === false) {
9605 this.$$element.removeAttr(attrName);
9606 } else {
9607 this.$$element.attr(attrName, value);
9608 }
9609 } else {
9610 setSpecialAttr(this.$$element[0], attrName, value);
9611 }
9612 }
9613 }
9614
9615 // fire observers
9616 var $$observers = this.$$observers;
9617 if ($$observers) {
9618 forEach($$observers[observer], function(fn) {
9619 try {
9620 fn(value);
9621 } catch (e) {
9622 $exceptionHandler(e);
9623 }
9624 });
9625 }
9626 },
9627
9628
9629 /**
9630 * @ngdoc method
9631 * @name $compile.directive.Attributes#$observe
9632 * @kind function
9633 *
9634 * @description
9635 * Observes an interpolated attribute.
9636 *
9637 * The observer function will be invoked once during the next `$digest` following
9638 * compilation. The observer is then invoked whenever the interpolated value
9639 * changes.
9640 *
9641 * @param {string} key Normalized key. (ie ngAttribute) .
9642 * @param {function(interpolatedValue)} fn Function that will be called whenever
9643 the interpolated value of the attribute changes.
9644 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
9645 * guide} for more info.
9646 * @returns {function()} Returns a deregistration function for this observer.
9647 */
9648 $observe: function(key, fn) {
9649 var attrs = this,
9650 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
9651 listeners = ($$observers[key] || ($$observers[key] = []));
9652
9653 listeners.push(fn);
9654 $rootScope.$evalAsync(function() {
9655 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
9656 // no one registered attribute interpolation function, so lets call it manually
9657 fn(attrs[key]);
9658 }
9659 });
9660
9661 return function() {
9662 arrayRemove(listeners, fn);
9663 };
9664 }
9665 };
9666
9667 function setSpecialAttr(element, attrName, value) {
9668 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
9669 // so we have to jump through some hoops to get such an attribute
9670 // https://github.com/angular/angular.js/pull/13318
9671 specialAttrHolder.innerHTML = '<span ' + attrName + '>';
9672 var attributes = specialAttrHolder.firstChild.attributes;
9673 var attribute = attributes[0];
9674 // We have to remove the attribute from its container element before we can add it to the destination element
9675 attributes.removeNamedItem(attribute.name);
9676 attribute.value = value;
9677 element.attributes.setNamedItem(attribute);
9678 }
9679
9680 function safeAddClass($element, className) {
9681 try {
9682 $element.addClass(className);
9683 } catch (e) {
9684 // ignore, since it means that we are trying to set class on
9685 // SVG element, where class name is read-only.
9686 }
9687 }
9688
9689
9690 var startSymbol = $interpolate.startSymbol(),
9691 endSymbol = $interpolate.endSymbol(),
9692 denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}')
9693 ? identity
9694 : function denormalizeTemplate(template) {
9695 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
9696 },
9697 NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/;
9698 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
9699
9700 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
9701 var bindings = $element.data('$binding') || [];
9702
9703 if (isArray(binding)) {
9704 bindings = bindings.concat(binding);
9705 } else {
9706 bindings.push(binding);
9707 }
9708
9709 $element.data('$binding', bindings);
9710 } : noop;
9711
9712 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
9713 safeAddClass($element, 'ng-binding');
9714 } : noop;
9715
9716 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
9717 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
9718 $element.data(dataName, scope);
9719 } : noop;
9720
9721 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
9722 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
9723 } : noop;
9724
9725 compile.$$createComment = function(directiveName, comment) {
9726 var content = '';
9727 if (debugInfoEnabled) {
9728 content = ' ' + (directiveName || '') + ': ';
9729 if (comment) content += comment + ' ';
9730 }
9731 return window.document.createComment(content);
9732 };
9733
9734 return compile;
9735
9736 //================================
9737
9738 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
9739 previousCompileContext) {
9740 if (!($compileNodes instanceof jqLite)) {
9741 // jquery always rewraps, whereas we need to preserve the original selector so that we can
9742 // modify it.
9743 $compileNodes = jqLite($compileNodes);
9744 }
9745 var compositeLinkFn =
9746 compileNodes($compileNodes, transcludeFn, $compileNodes,
9747 maxPriority, ignoreDirective, previousCompileContext);
9748 compile.$$addScopeClass($compileNodes);
9749 var namespace = null;
9750 return function publicLinkFn(scope, cloneConnectFn, options) {
9751 if (!$compileNodes) {
9752 throw $compileMinErr('multilink', 'This element has already been linked.');
9753 }
9754 assertArg(scope, 'scope');
9755
9756 if (previousCompileContext && previousCompileContext.needsNewScope) {
9757 // A parent directive did a replace and a directive on this element asked
9758 // for transclusion, which caused us to lose a layer of element on which
9759 // we could hold the new transclusion scope, so we will create it manually
9760 // here.
9761 scope = scope.$parent.$new();
9762 }
9763
9764 options = options || {};
9765 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
9766 transcludeControllers = options.transcludeControllers,
9767 futureParentElement = options.futureParentElement;
9768
9769 // When `parentBoundTranscludeFn` is passed, it is a
9770 // `controllersBoundTransclude` function (it was previously passed
9771 // as `transclude` to directive.link) so we must unwrap it to get
9772 // its `boundTranscludeFn`
9773 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
9774 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
9775 }
9776
9777 if (!namespace) {
9778 namespace = detectNamespaceForChildElements(futureParentElement);
9779 }
9780 var $linkNode;
9781 if (namespace !== 'html') {
9782 // When using a directive with replace:true and templateUrl the $compileNodes
9783 // (or a child element inside of them)
9784 // might change, so we need to recreate the namespace adapted compileNodes
9785 // for call to the link function.
9786 // Note: This will already clone the nodes...
9787 $linkNode = jqLite(
9788 wrapTemplate(namespace, jqLite('<div></div>').append($compileNodes).html())
9789 );
9790 } else if (cloneConnectFn) {
9791 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
9792 // and sometimes changes the structure of the DOM.
9793 $linkNode = JQLitePrototype.clone.call($compileNodes);
9794 } else {
9795 $linkNode = $compileNodes;
9796 }
9797
9798 if (transcludeControllers) {
9799 for (var controllerName in transcludeControllers) {
9800 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
9801 }
9802 }
9803
9804 compile.$$addScopeInfo($linkNode, scope);
9805
9806 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
9807 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
9808
9809 if (!cloneConnectFn) {
9810 $compileNodes = compositeLinkFn = null;
9811 }
9812 return $linkNode;
9813 };
9814 }
9815
9816 function detectNamespaceForChildElements(parentElement) {
9817 // TODO: Make this detect MathML as well...
9818 var node = parentElement && parentElement[0];
9819 if (!node) {
9820 return 'html';
9821 } else {
9822 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html';
9823 }
9824 }
9825
9826 /**
9827 * Compile function matches each node in nodeList against the directives. Once all directives
9828 * for a particular node are collected their compile functions are executed. The compile
9829 * functions return values - the linking functions - are combined into a composite linking
9830 * function, which is the a linking function for the node.
9831 *
9832 * @param {NodeList} nodeList an array of nodes or NodeList to compile
9833 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
9834 * scope argument is auto-generated to the new child of the transcluded parent scope.
9835 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
9836 * the rootElement must be set the jqLite collection of the compile root. This is
9837 * needed so that the jqLite collection items can be replaced with widgets.
9838 * @param {number=} maxPriority Max directive priority.
9839 * @returns {Function} A composite linking function of all of the matched directives or null.
9840 */
9841 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
9842 previousCompileContext) {
9843 var linkFns = [],
9844 // `nodeList` can be either an element's `.childNodes` (live NodeList)
9845 // or a jqLite/jQuery collection or an array
9846 notLiveList = isArray(nodeList) || (nodeList instanceof jqLite),
9847 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
9848
9849
9850 for (var i = 0; i < nodeList.length; i++) {
9851 attrs = new Attributes();
9852
9853 // Support: IE 11 only
9854 // Workaround for #11781 and #14924
9855 if (msie === 11) {
9856 mergeConsecutiveTextNodes(nodeList, i, notLiveList);
9857 }
9858
9859 // We must always refer to `nodeList[i]` hereafter,
9860 // since the nodes can be replaced underneath us.
9861 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
9862 ignoreDirective);
9863
9864 nodeLinkFn = (directives.length)
9865 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
9866 null, [], [], previousCompileContext)
9867 : null;
9868
9869 if (nodeLinkFn && nodeLinkFn.scope) {
9870 compile.$$addScopeClass(attrs.$$element);
9871 }
9872
9873 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
9874 !(childNodes = nodeList[i].childNodes) ||
9875 !childNodes.length)
9876 ? null
9877 : compileNodes(childNodes,
9878 nodeLinkFn ? (
9879 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
9880 && nodeLinkFn.transclude) : transcludeFn);
9881
9882 if (nodeLinkFn || childLinkFn) {
9883 linkFns.push(i, nodeLinkFn, childLinkFn);
9884 linkFnFound = true;
9885 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
9886 }
9887
9888 //use the previous context only for the first element in the virtual group
9889 previousCompileContext = null;
9890 }
9891
9892 // return a linking function if we have found anything, null otherwise
9893 return linkFnFound ? compositeLinkFn : null;
9894
9895 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
9896 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
9897 var stableNodeList;
9898
9899
9900 if (nodeLinkFnFound) {
9901 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
9902 // offsets don't get screwed up
9903 var nodeListLength = nodeList.length;
9904 stableNodeList = new Array(nodeListLength);
9905
9906 // create a sparse array by only copying the elements which have a linkFn
9907 for (i = 0; i < linkFns.length; i += 3) {
9908 idx = linkFns[i];
9909 stableNodeList[idx] = nodeList[idx];
9910 }
9911 } else {
9912 stableNodeList = nodeList;
9913 }
9914
9915 for (i = 0, ii = linkFns.length; i < ii;) {
9916 node = stableNodeList[linkFns[i++]];
9917 nodeLinkFn = linkFns[i++];
9918 childLinkFn = linkFns[i++];
9919
9920 if (nodeLinkFn) {
9921 if (nodeLinkFn.scope) {
9922 childScope = scope.$new();
9923 compile.$$addScopeInfo(jqLite(node), childScope);
9924 } else {
9925 childScope = scope;
9926 }
9927
9928 if (nodeLinkFn.transcludeOnThisElement) {
9929 childBoundTranscludeFn = createBoundTranscludeFn(
9930 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
9931
9932 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
9933 childBoundTranscludeFn = parentBoundTranscludeFn;
9934
9935 } else if (!parentBoundTranscludeFn && transcludeFn) {
9936 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
9937
9938 } else {
9939 childBoundTranscludeFn = null;
9940 }
9941
9942 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
9943
9944 } else if (childLinkFn) {
9945 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
9946 }
9947 }
9948 }
9949 }
9950
9951 function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) {
9952 var node = nodeList[idx];
9953 var parent = node.parentNode;
9954 var sibling;
9955
9956 if (node.nodeType !== NODE_TYPE_TEXT) {
9957 return;
9958 }
9959
9960 while (true) {
9961 sibling = parent ? node.nextSibling : nodeList[idx + 1];
9962 if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) {
9963 break;
9964 }
9965
9966 node.nodeValue = node.nodeValue + sibling.nodeValue;
9967
9968 if (sibling.parentNode) {
9969 sibling.parentNode.removeChild(sibling);
9970 }
9971 if (notLiveList && sibling === nodeList[idx + 1]) {
9972 nodeList.splice(idx + 1, 1);
9973 }
9974 }
9975 }
9976
9977 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
9978 function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
9979
9980 if (!transcludedScope) {
9981 transcludedScope = scope.$new(false, containingScope);
9982 transcludedScope.$$transcluded = true;
9983 }
9984
9985 return transcludeFn(transcludedScope, cloneFn, {
9986 parentBoundTranscludeFn: previousBoundTranscludeFn,
9987 transcludeControllers: controllers,
9988 futureParentElement: futureParentElement
9989 });
9990 }
9991
9992 // We need to attach the transclusion slots onto the `boundTranscludeFn`
9993 // so that they are available inside the `controllersBoundTransclude` function
9994 var boundSlots = boundTranscludeFn.$$slots = createMap();
9995 for (var slotName in transcludeFn.$$slots) {
9996 if (transcludeFn.$$slots[slotName]) {
9997 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
9998 } else {
9999 boundSlots[slotName] = null;
10000 }
10001 }
10002
10003 return boundTranscludeFn;
10004 }
10005
10006 /**
10007 * Looks for directives on the given node and adds them to the directive collection which is
10008 * sorted.
10009 *
10010 * @param node Node to search.
10011 * @param directives An array to which the directives are added to. This array is sorted before
10012 * the function returns.
10013 * @param attrs The shared attrs object which is used to populate the normalized attributes.
10014 * @param {number=} maxPriority Max directive priority.
10015 */
10016 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
10017 var nodeType = node.nodeType,
10018 attrsMap = attrs.$attr,
10019 match,
10020 nodeName,
10021 className;
10022
10023 switch (nodeType) {
10024 case NODE_TYPE_ELEMENT: /* Element */
10025
10026 nodeName = nodeName_(node);
10027
10028 // use the node name: <directive>
10029 addDirective(directives,
10030 directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);
10031
10032 // iterate over the attributes
10033 for (var attr, name, nName, value, ngPrefixMatch, nAttrs = node.attributes,
10034 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
10035 var attrStartName = false;
10036 var attrEndName = false;
10037
10038 var isNgAttr = false, isNgProp = false, isNgEvent = false;
10039 var multiElementMatch;
10040
10041 attr = nAttrs[j];
10042 name = attr.name;
10043 value = attr.value;
10044
10045 nName = directiveNormalize(name.toLowerCase());
10046
10047 // Support ng-attr-*, ng-prop-* and ng-on-*
10048 if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) {
10049 isNgAttr = ngPrefixMatch[1] === 'Attr';
10050 isNgProp = ngPrefixMatch[1] === 'Prop';
10051 isNgEvent = ngPrefixMatch[1] === 'On';
10052
10053 // Normalize the non-prefixed name
10054 name = name.replace(PREFIX_REGEXP, '')
10055 .toLowerCase()
10056 .substr(4 + ngPrefixMatch[1].length).replace(/_(.)/g, function(match, letter) {
10057 return letter.toUpperCase();
10058 });
10059
10060 // Support *-start / *-end multi element directives
10061 } else if ((multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) && directiveIsMultiElement(multiElementMatch[1])) {
10062 attrStartName = name;
10063 attrEndName = name.substr(0, name.length - 5) + 'end';
10064 name = name.substr(0, name.length - 6);
10065 }
10066
10067 if (isNgProp || isNgEvent) {
10068 attrs[nName] = value;
10069 attrsMap[nName] = attr.name;
10070
10071 if (isNgProp) {
10072 addPropertyDirective(node, directives, nName, name);
10073 } else {
10074 addEventDirective(directives, nName, name);
10075 }
10076 } else {
10077 // Update nName for cases where a prefix was removed
10078 // NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-*
10079 nName = directiveNormalize(name.toLowerCase());
10080 attrsMap[nName] = name;
10081
10082 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
10083 attrs[nName] = value;
10084 if (getBooleanAttrName(node, nName)) {
10085 attrs[nName] = true; // presence means true
10086 }
10087 }
10088
10089 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
10090 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
10091 attrEndName);
10092 }
10093 }
10094
10095 if (nodeName === 'input' && node.getAttribute('type') === 'hidden') {
10096 // Hidden input elements can have strange behaviour when navigating back to the page
10097 // This tells the browser not to try to cache and reinstate previous values
10098 node.setAttribute('autocomplete', 'off');
10099 }
10100
10101 // use class as directive
10102 if (!cssClassDirectivesEnabled) break;
10103 className = node.className;
10104 if (isObject(className)) {
10105 // Maybe SVGAnimatedString
10106 className = className.animVal;
10107 }
10108 if (isString(className) && className !== '') {
10109 while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) {
10110 nName = directiveNormalize(match[2]);
10111 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
10112 attrs[nName] = trim(match[3]);
10113 }
10114 className = className.substr(match.index + match[0].length);
10115 }
10116 }
10117 break;
10118 case NODE_TYPE_TEXT: /* Text Node */
10119 addTextInterpolateDirective(directives, node.nodeValue);
10120 break;
10121 case NODE_TYPE_COMMENT: /* Comment */
10122 if (!commentDirectivesEnabled) break;
10123 collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective);
10124 break;
10125 }
10126
10127 directives.sort(byPriority);
10128 return directives;
10129 }
10130
10131 function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
10132 // function created because of performance, try/catch disables
10133 // the optimization of the whole function #14848
10134 try {
10135 var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
10136 if (match) {
10137 var nName = directiveNormalize(match[1]);
10138 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
10139 attrs[nName] = trim(match[2]);
10140 }
10141 }
10142 } catch (e) {
10143 // turns out that under some circumstances IE9 throws errors when one attempts to read
10144 // comment's node value.
10145 // Just ignore it and continue. (Can't seem to reproduce in test case.)
10146 }
10147 }
10148
10149 /**
10150 * Given a node with a directive-start it collects all of the siblings until it finds
10151 * directive-end.
10152 * @param node
10153 * @param attrStart
10154 * @param attrEnd
10155 * @returns {*}
10156 */
10157 function groupScan(node, attrStart, attrEnd) {
10158 var nodes = [];
10159 var depth = 0;
10160 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
10161 do {
10162 if (!node) {
10163 throw $compileMinErr('uterdir',
10164 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.',
10165 attrStart, attrEnd);
10166 }
10167 if (node.nodeType === NODE_TYPE_ELEMENT) {
10168 if (node.hasAttribute(attrStart)) depth++;
10169 if (node.hasAttribute(attrEnd)) depth--;
10170 }
10171 nodes.push(node);
10172 node = node.nextSibling;
10173 } while (depth > 0);
10174 } else {
10175 nodes.push(node);
10176 }
10177
10178 return jqLite(nodes);
10179 }
10180
10181 /**
10182 * Wrapper for linking function which converts normal linking function into a grouped
10183 * linking function.
10184 * @param linkFn
10185 * @param attrStart
10186 * @param attrEnd
10187 * @returns {Function}
10188 */
10189 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
10190 return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) {
10191 element = groupScan(element[0], attrStart, attrEnd);
10192 return linkFn(scope, element, attrs, controllers, transcludeFn);
10193 };
10194 }
10195
10196 /**
10197 * A function generator that is used to support both eager and lazy compilation
10198 * linking function.
10199 * @param eager
10200 * @param $compileNodes
10201 * @param transcludeFn
10202 * @param maxPriority
10203 * @param ignoreDirective
10204 * @param previousCompileContext
10205 * @returns {Function}
10206 */
10207 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
10208 var compiled;
10209
10210 if (eager) {
10211 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
10212 }
10213 return /** @this */ function lazyCompilation() {
10214 if (!compiled) {
10215 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
10216
10217 // Null out all of these references in order to make them eligible for garbage collection
10218 // since this is a potentially long lived closure
10219 $compileNodes = transcludeFn = previousCompileContext = null;
10220 }
10221 return compiled.apply(this, arguments);
10222 };
10223 }
10224
10225 /**
10226 * Once the directives have been collected, their compile functions are executed. This method
10227 * is responsible for inlining directive templates as well as terminating the application
10228 * of the directives if the terminal directive has been reached.
10229 *
10230 * @param {Array} directives Array of collected directives to execute their compile function.
10231 * this needs to be pre-sorted by priority order.
10232 * @param {Node} compileNode The raw DOM node to apply the compile functions to
10233 * @param {Object} templateAttrs The shared attribute function
10234 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
10235 * scope argument is auto-generated to the new
10236 * child of the transcluded parent scope.
10237 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
10238 * argument has the root jqLite array so that we can replace nodes
10239 * on it.
10240 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
10241 * compiling the transclusion.
10242 * @param {Array.<Function>} preLinkFns
10243 * @param {Array.<Function>} postLinkFns
10244 * @param {Object} previousCompileContext Context used for previous compilation of the current
10245 * node
10246 * @returns {Function} linkFn
10247 */
10248 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
10249 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
10250 previousCompileContext) {
10251 previousCompileContext = previousCompileContext || {};
10252
10253 var terminalPriority = -Number.MAX_VALUE,
10254 newScopeDirective = previousCompileContext.newScopeDirective,
10255 controllerDirectives = previousCompileContext.controllerDirectives,
10256 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
10257 templateDirective = previousCompileContext.templateDirective,
10258 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
10259 hasTranscludeDirective = false,
10260 hasTemplate = false,
10261 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
10262 $compileNode = templateAttrs.$$element = jqLite(compileNode),
10263 directive,
10264 directiveName,
10265 $template,
10266 replaceDirective = originalReplaceDirective,
10267 childTranscludeFn = transcludeFn,
10268 linkFn,
10269 didScanForMultipleTransclusion = false,
10270 mightHaveMultipleTransclusionError = false,
10271 directiveValue;
10272
10273 // executes all directives on the current element
10274 for (var i = 0, ii = directives.length; i < ii; i++) {
10275 directive = directives[i];
10276 var attrStart = directive.$$start;
10277 var attrEnd = directive.$$end;
10278
10279 // collect multiblock sections
10280 if (attrStart) {
10281 $compileNode = groupScan(compileNode, attrStart, attrEnd);
10282 }
10283 $template = undefined;
10284
10285 if (terminalPriority > directive.priority) {
10286 break; // prevent further processing of directives
10287 }
10288
10289 directiveValue = directive.scope;
10290
10291 if (directiveValue) {
10292
10293 // skip the check for directives with async templates, we'll check the derived sync
10294 // directive when the template arrives
10295 if (!directive.templateUrl) {
10296 if (isObject(directiveValue)) {
10297 // This directive is trying to add an isolated scope.
10298 // Check that there is no scope of any kind already
10299 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
10300 directive, $compileNode);
10301 newIsolateScopeDirective = directive;
10302 } else {
10303 // This directive is trying to add a child scope.
10304 // Check that there is no isolated scope already
10305 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
10306 $compileNode);
10307 }
10308 }
10309
10310 newScopeDirective = newScopeDirective || directive;
10311 }
10312
10313 directiveName = directive.name;
10314
10315 // If we encounter a condition that can result in transclusion on the directive,
10316 // then scan ahead in the remaining directives for others that may cause a multiple
10317 // transclusion error to be thrown during the compilation process. If a matching directive
10318 // is found, then we know that when we encounter a transcluded directive, we need to eagerly
10319 // compile the `transclude` function rather than doing it lazily in order to throw
10320 // exceptions at the correct time
10321 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template))
10322 || (directive.transclude && !directive.$$tlb))) {
10323 var candidateDirective;
10324
10325 for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) {
10326 if ((candidateDirective.transclude && !candidateDirective.$$tlb)
10327 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) {
10328 mightHaveMultipleTransclusionError = true;
10329 break;
10330 }
10331 }
10332
10333 didScanForMultipleTransclusion = true;
10334 }
10335
10336 if (!directive.templateUrl && directive.controller) {
10337 controllerDirectives = controllerDirectives || createMap();
10338 assertNoDuplicate('\'' + directiveName + '\' controller',
10339 controllerDirectives[directiveName], directive, $compileNode);
10340 controllerDirectives[directiveName] = directive;
10341 }
10342
10343 directiveValue = directive.transclude;
10344
10345 if (directiveValue) {
10346 hasTranscludeDirective = true;
10347
10348 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
10349 // This option should only be used by directives that know how to safely handle element transclusion,
10350 // where the transcluded nodes are added or replaced after linking.
10351 if (!directive.$$tlb) {
10352 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
10353 nonTlbTranscludeDirective = directive;
10354 }
10355
10356 if (directiveValue === 'element') {
10357 hasElementTranscludeDirective = true;
10358 terminalPriority = directive.priority;
10359 $template = $compileNode;
10360 $compileNode = templateAttrs.$$element =
10361 jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName]));
10362 compileNode = $compileNode[0];
10363 replaceWith(jqCollection, sliceArgs($template), compileNode);
10364
10365 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
10366 replaceDirective && replaceDirective.name, {
10367 // Don't pass in:
10368 // - controllerDirectives - otherwise we'll create duplicates controllers
10369 // - newIsolateScopeDirective or templateDirective - combining templates with
10370 // element transclusion doesn't make sense.
10371 //
10372 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
10373 // on the same element more than once.
10374 nonTlbTranscludeDirective: nonTlbTranscludeDirective
10375 });
10376 } else {
10377
10378 var slots = createMap();
10379
10380 if (!isObject(directiveValue)) {
10381 $template = jqLite(jqLiteClone(compileNode)).contents();
10382 } else {
10383
10384 // We have transclusion slots,
10385 // collect them up, compile them and store their transclusion functions
10386 $template = window.document.createDocumentFragment();
10387
10388 var slotMap = createMap();
10389 var filledSlots = createMap();
10390
10391 // Parse the element selectors
10392 forEach(directiveValue, function(elementSelector, slotName) {
10393 // If an element selector starts with a ? then it is optional
10394 var optional = (elementSelector.charAt(0) === '?');
10395 elementSelector = optional ? elementSelector.substring(1) : elementSelector;
10396
10397 slotMap[elementSelector] = slotName;
10398
10399 // We explicitly assign `null` since this implies that a slot was defined but not filled.
10400 // Later when calling boundTransclusion functions with a slot name we only error if the
10401 // slot is `undefined`
10402 slots[slotName] = null;
10403
10404 // filledSlots contains `true` for all slots that are either optional or have been
10405 // filled. This is used to check that we have not missed any required slots
10406 filledSlots[slotName] = optional;
10407 });
10408
10409 // Add the matching elements into their slot
10410 forEach($compileNode.contents(), function(node) {
10411 var slotName = slotMap[directiveNormalize(nodeName_(node))];
10412 if (slotName) {
10413 filledSlots[slotName] = true;
10414 slots[slotName] = slots[slotName] || window.document.createDocumentFragment();
10415 slots[slotName].appendChild(node);
10416 } else {
10417 $template.appendChild(node);
10418 }
10419 });
10420
10421 // Check for required slots that were not filled
10422 forEach(filledSlots, function(filled, slotName) {
10423 if (!filled) {
10424 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName);
10425 }
10426 });
10427
10428 for (var slotName in slots) {
10429 if (slots[slotName]) {
10430 // Only define a transclusion function if the slot was filled
10431 var slotCompileNodes = jqLite(slots[slotName].childNodes);
10432 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slotCompileNodes, transcludeFn);
10433 }
10434 }
10435
10436 $template = jqLite($template.childNodes);
10437 }
10438
10439 $compileNode.empty(); // clear contents
10440 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined,
10441 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
10442 childTranscludeFn.$$slots = slots;
10443 }
10444 }
10445
10446 if (directive.template) {
10447 hasTemplate = true;
10448 assertNoDuplicate('template', templateDirective, directive, $compileNode);
10449 templateDirective = directive;
10450
10451 directiveValue = (isFunction(directive.template))
10452 ? directive.template($compileNode, templateAttrs)
10453 : directive.template;
10454
10455 directiveValue = denormalizeTemplate(directiveValue);
10456
10457 if (directive.replace) {
10458 replaceDirective = directive;
10459 if (jqLiteIsTextNode(directiveValue)) {
10460 $template = [];
10461 } else {
10462 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
10463 }
10464 compileNode = $template[0];
10465
10466 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
10467 throw $compileMinErr('tplrt',
10468 'Template for directive \'{0}\' must have exactly one root element. {1}',
10469 directiveName, '');
10470 }
10471
10472 replaceWith(jqCollection, $compileNode, compileNode);
10473
10474 var newTemplateAttrs = {$attr: {}};
10475
10476 // combine directives from the original node and from the template:
10477 // - take the array of directives for this element
10478 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
10479 // - collect directives from the template and sort them by priority
10480 // - combine directives as: processed + template + unprocessed
10481 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
10482 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
10483
10484 if (newIsolateScopeDirective || newScopeDirective) {
10485 // The original directive caused the current element to be replaced but this element
10486 // also needs to have a new scope, so we need to tell the template directives
10487 // that they would need to get their scope from further up, if they require transclusion
10488 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
10489 }
10490 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
10491 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
10492
10493 ii = directives.length;
10494 } else {
10495 $compileNode.html(directiveValue);
10496 }
10497 }
10498
10499 if (directive.templateUrl) {
10500 hasTemplate = true;
10501 assertNoDuplicate('template', templateDirective, directive, $compileNode);
10502 templateDirective = directive;
10503
10504 if (directive.replace) {
10505 replaceDirective = directive;
10506 }
10507
10508 // eslint-disable-next-line no-func-assign
10509 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
10510 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
10511 controllerDirectives: controllerDirectives,
10512 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
10513 newIsolateScopeDirective: newIsolateScopeDirective,
10514 templateDirective: templateDirective,
10515 nonTlbTranscludeDirective: nonTlbTranscludeDirective
10516 });
10517 ii = directives.length;
10518 } else if (directive.compile) {
10519 try {
10520 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
10521 var context = directive.$$originalDirective || directive;
10522 if (isFunction(linkFn)) {
10523 addLinkFns(null, bind(context, linkFn), attrStart, attrEnd);
10524 } else if (linkFn) {
10525 addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd);
10526 }
10527 } catch (e) {
10528 $exceptionHandler(e, startingTag($compileNode));
10529 }
10530 }
10531
10532 if (directive.terminal) {
10533 nodeLinkFn.terminal = true;
10534 terminalPriority = Math.max(terminalPriority, directive.priority);
10535 }
10536
10537 }
10538
10539 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
10540 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
10541 nodeLinkFn.templateOnThisElement = hasTemplate;
10542 nodeLinkFn.transclude = childTranscludeFn;
10543
10544 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
10545
10546 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
10547 return nodeLinkFn;
10548
10549 ////////////////////
10550
10551 function addLinkFns(pre, post, attrStart, attrEnd) {
10552 if (pre) {
10553 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
10554 pre.require = directive.require;
10555 pre.directiveName = directiveName;
10556 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
10557 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
10558 }
10559 preLinkFns.push(pre);
10560 }
10561 if (post) {
10562 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
10563 post.require = directive.require;
10564 post.directiveName = directiveName;
10565 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
10566 post = cloneAndAnnotateFn(post, {isolateScope: true});
10567 }
10568 postLinkFns.push(post);
10569 }
10570 }
10571
10572 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
10573 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
10574 attrs, scopeBindingInfo;
10575
10576 if (compileNode === linkNode) {
10577 attrs = templateAttrs;
10578 $element = templateAttrs.$$element;
10579 } else {
10580 $element = jqLite(linkNode);
10581 attrs = new Attributes($element, templateAttrs);
10582 }
10583
10584 controllerScope = scope;
10585 if (newIsolateScopeDirective) {
10586 isolateScope = scope.$new(true);
10587 } else if (newScopeDirective) {
10588 controllerScope = scope.$parent;
10589 }
10590
10591 if (boundTranscludeFn) {
10592 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
10593 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
10594 transcludeFn = controllersBoundTransclude;
10595 transcludeFn.$$boundTransclude = boundTranscludeFn;
10596 // expose the slots on the `$transclude` function
10597 transcludeFn.isSlotFilled = function(slotName) {
10598 return !!boundTranscludeFn.$$slots[slotName];
10599 };
10600 }
10601
10602 if (controllerDirectives) {
10603 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective);
10604 }
10605
10606 if (newIsolateScopeDirective) {
10607 // Initialize isolate scope bindings for new isolate scope directive.
10608 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
10609 templateDirective === newIsolateScopeDirective.$$originalDirective)));
10610 compile.$$addScopeClass($element, true);
10611 isolateScope.$$isolateBindings =
10612 newIsolateScopeDirective.$$isolateBindings;
10613 scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope,
10614 isolateScope.$$isolateBindings,
10615 newIsolateScopeDirective);
10616 if (scopeBindingInfo.removeWatches) {
10617 isolateScope.$on('$destroy', scopeBindingInfo.removeWatches);
10618 }
10619 }
10620
10621 // Initialize bindToController bindings
10622 for (var name in elementControllers) {
10623 var controllerDirective = controllerDirectives[name];
10624 var controller = elementControllers[name];
10625 var bindings = controllerDirective.$$bindings.bindToController;
10626
10627 controller.instance = controller();
10628 $element.data('$' + controllerDirective.name + 'Controller', controller.instance);
10629 controller.bindingInfo =
10630 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
10631 }
10632
10633 // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
10634 forEach(controllerDirectives, function(controllerDirective, name) {
10635 var require = controllerDirective.require;
10636 if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
10637 extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
10638 }
10639 });
10640
10641 // Handle the init and destroy lifecycle hooks on all controllers that have them
10642 forEach(elementControllers, function(controller) {
10643 var controllerInstance = controller.instance;
10644 if (isFunction(controllerInstance.$onChanges)) {
10645 try {
10646 controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
10647 } catch (e) {
10648 $exceptionHandler(e);
10649 }
10650 }
10651 if (isFunction(controllerInstance.$onInit)) {
10652 try {
10653 controllerInstance.$onInit();
10654 } catch (e) {
10655 $exceptionHandler(e);
10656 }
10657 }
10658 if (isFunction(controllerInstance.$doCheck)) {
10659 controllerScope.$watch(function() { controllerInstance.$doCheck(); });
10660 controllerInstance.$doCheck();
10661 }
10662 if (isFunction(controllerInstance.$onDestroy)) {
10663 controllerScope.$on('$destroy', function callOnDestroyHook() {
10664 controllerInstance.$onDestroy();
10665 });
10666 }
10667 });
10668
10669 // PRELINKING
10670 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
10671 linkFn = preLinkFns[i];
10672 invokeLinkFn(linkFn,
10673 linkFn.isolateScope ? isolateScope : scope,
10674 $element,
10675 attrs,
10676 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
10677 transcludeFn
10678 );
10679 }
10680
10681 // RECURSION
10682 // We only pass the isolate scope, if the isolate directive has a template,
10683 // otherwise the child elements do not belong to the isolate directive.
10684 var scopeToChild = scope;
10685 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
10686 scopeToChild = isolateScope;
10687 }
10688 if (childLinkFn) {
10689 childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
10690 }
10691
10692 // POSTLINKING
10693 for (i = postLinkFns.length - 1; i >= 0; i--) {
10694 linkFn = postLinkFns[i];
10695 invokeLinkFn(linkFn,
10696 linkFn.isolateScope ? isolateScope : scope,
10697 $element,
10698 attrs,
10699 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
10700 transcludeFn
10701 );
10702 }
10703
10704 // Trigger $postLink lifecycle hooks
10705 forEach(elementControllers, function(controller) {
10706 var controllerInstance = controller.instance;
10707 if (isFunction(controllerInstance.$postLink)) {
10708 controllerInstance.$postLink();
10709 }
10710 });
10711
10712 // This is the function that is injected as `$transclude`.
10713 // Note: all arguments are optional!
10714 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
10715 var transcludeControllers;
10716 // No scope passed in:
10717 if (!isScope(scope)) {
10718 slotName = futureParentElement;
10719 futureParentElement = cloneAttachFn;
10720 cloneAttachFn = scope;
10721 scope = undefined;
10722 }
10723
10724 if (hasElementTranscludeDirective) {
10725 transcludeControllers = elementControllers;
10726 }
10727 if (!futureParentElement) {
10728 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
10729 }
10730 if (slotName) {
10731 // slotTranscludeFn can be one of three things:
10732 // * a transclude function - a filled slot
10733 // * `null` - an optional slot that was not filled
10734 // * `undefined` - a slot that was not declared (i.e. invalid)
10735 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
10736 if (slotTranscludeFn) {
10737 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
10738 } else if (isUndefined(slotTranscludeFn)) {
10739 throw $compileMinErr('noslot',
10740 'No parent directive that requires a transclusion with slot name "{0}". ' +
10741 'Element: {1}',
10742 slotName, startingTag($element));
10743 }
10744 } else {
10745 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
10746 }
10747 }
10748 }
10749 }
10750
10751 function getControllers(directiveName, require, $element, elementControllers) {
10752 var value;
10753
10754 if (isString(require)) {
10755 var match = require.match(REQUIRE_PREFIX_REGEXP);
10756 var name = require.substring(match[0].length);
10757 var inheritType = match[1] || match[3];
10758 var optional = match[2] === '?';
10759
10760 //If only parents then start at the parent element
10761 if (inheritType === '^^') {
10762 $element = $element.parent();
10763 //Otherwise attempt getting the controller from elementControllers in case
10764 //the element is transcluded (and has no data) and to avoid .data if possible
10765 } else {
10766 value = elementControllers && elementControllers[name];
10767 value = value && value.instance;
10768 }
10769
10770 if (!value) {
10771 var dataName = '$' + name + 'Controller';
10772
10773 if (inheritType === '^^' && $element[0] && $element[0].nodeType === NODE_TYPE_DOCUMENT) {
10774 // inheritedData() uses the documentElement when it finds the document, so we would
10775 // require from the element itself.
10776 value = null;
10777 } else {
10778 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
10779 }
10780 }
10781
10782 if (!value && !optional) {
10783 throw $compileMinErr('ctreq',
10784 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!',
10785 name, directiveName);
10786 }
10787 } else if (isArray(require)) {
10788 value = [];
10789 for (var i = 0, ii = require.length; i < ii; i++) {
10790 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
10791 }
10792 } else if (isObject(require)) {
10793 value = {};
10794 forEach(require, function(controller, property) {
10795 value[property] = getControllers(directiveName, controller, $element, elementControllers);
10796 });
10797 }
10798
10799 return value || null;
10800 }
10801
10802 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) {
10803 var elementControllers = createMap();
10804 for (var controllerKey in controllerDirectives) {
10805 var directive = controllerDirectives[controllerKey];
10806 var locals = {
10807 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
10808 $element: $element,
10809 $attrs: attrs,
10810 $transclude: transcludeFn
10811 };
10812
10813 var controller = directive.controller;
10814 if (controller === '@') {
10815 controller = attrs[directive.name];
10816 }
10817
10818 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
10819
10820 // For directives with element transclusion the element is a comment.
10821 // In this case .data will not attach any data.
10822 // Instead, we save the controllers for the element in a local hash and attach to .data
10823 // later, once we have the actual element.
10824 elementControllers[directive.name] = controllerInstance;
10825 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
10826 }
10827 return elementControllers;
10828 }
10829
10830 // Depending upon the context in which a directive finds itself it might need to have a new isolated
10831 // or child scope created. For instance:
10832 // * if the directive has been pulled into a template because another directive with a higher priority
10833 // asked for element transclusion
10834 // * if the directive itself asks for transclusion but it is at the root of a template and the original
10835 // element was replaced. See https://github.com/angular/angular.js/issues/12936
10836 function markDirectiveScope(directives, isolateScope, newScope) {
10837 for (var j = 0, jj = directives.length; j < jj; j++) {
10838 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
10839 }
10840 }
10841
10842 /**
10843 * looks up the directive and decorates it with exception handling and proper parameters. We
10844 * call this the boundDirective.
10845 *
10846 * @param {string} name name of the directive to look up.
10847 * @param {string} location The directive must be found in specific format.
10848 * String containing any of theses characters:
10849 *
10850 * * `E`: element name
10851 * * `A': attribute
10852 * * `C`: class
10853 * * `M`: comment
10854 * @returns {boolean} true if directive was added.
10855 */
10856 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
10857 endAttrName) {
10858 if (name === ignoreDirective) return null;
10859 var match = null;
10860 if (hasDirectives.hasOwnProperty(name)) {
10861 for (var directive, directives = $injector.get(name + Suffix),
10862 i = 0, ii = directives.length; i < ii; i++) {
10863 directive = directives[i];
10864 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
10865 directive.restrict.indexOf(location) !== -1) {
10866 if (startAttrName) {
10867 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
10868 }
10869 if (!directive.$$bindings) {
10870 var bindings = directive.$$bindings =
10871 parseDirectiveBindings(directive, directive.name);
10872 if (isObject(bindings.isolateScope)) {
10873 directive.$$isolateBindings = bindings.isolateScope;
10874 }
10875 }
10876 tDirectives.push(directive);
10877 match = directive;
10878 }
10879 }
10880 }
10881 return match;
10882 }
10883
10884
10885 /**
10886 * looks up the directive and returns true if it is a multi-element directive,
10887 * and therefore requires DOM nodes between -start and -end markers to be grouped
10888 * together.
10889 *
10890 * @param {string} name name of the directive to look up.
10891 * @returns true if directive was registered as multi-element.
10892 */
10893 function directiveIsMultiElement(name) {
10894 if (hasDirectives.hasOwnProperty(name)) {
10895 for (var directive, directives = $injector.get(name + Suffix),
10896 i = 0, ii = directives.length; i < ii; i++) {
10897 directive = directives[i];
10898 if (directive.multiElement) {
10899 return true;
10900 }
10901 }
10902 }
10903 return false;
10904 }
10905
10906 /**
10907 * When the element is replaced with HTML template then the new attributes
10908 * on the template need to be merged with the existing attributes in the DOM.
10909 * The desired effect is to have both of the attributes present.
10910 *
10911 * @param {object} dst destination attributes (original DOM)
10912 * @param {object} src source attributes (from the directive template)
10913 */
10914 function mergeTemplateAttributes(dst, src) {
10915 var srcAttr = src.$attr,
10916 dstAttr = dst.$attr;
10917
10918 // reapply the old attributes to the new element
10919 forEach(dst, function(value, key) {
10920 if (key.charAt(0) !== '$') {
10921 if (src[key] && src[key] !== value) {
10922 if (value.length) {
10923 value += (key === 'style' ? ';' : ' ') + src[key];
10924 } else {
10925 value = src[key];
10926 }
10927 }
10928 dst.$set(key, value, true, srcAttr[key]);
10929 }
10930 });
10931
10932 // copy the new attributes on the old attrs object
10933 forEach(src, function(value, key) {
10934 // Check if we already set this attribute in the loop above.
10935 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
10936 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
10937 // have an attribute like "has-own-property" or "data-has-own-property", etc.
10938 if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') {
10939 dst[key] = value;
10940
10941 if (key !== 'class' && key !== 'style') {
10942 dstAttr[key] = srcAttr[key];
10943 }
10944 }
10945 });
10946 }
10947
10948
10949 function compileTemplateUrl(directives, $compileNode, tAttrs,
10950 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
10951 var linkQueue = [],
10952 afterTemplateNodeLinkFn,
10953 afterTemplateChildLinkFn,
10954 beforeTemplateCompileNode = $compileNode[0],
10955 origAsyncDirective = directives.shift(),
10956 derivedSyncDirective = inherit(origAsyncDirective, {
10957 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
10958 }),
10959 templateUrl = (isFunction(origAsyncDirective.templateUrl))
10960 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
10961 : origAsyncDirective.templateUrl,
10962 templateNamespace = origAsyncDirective.templateNamespace;
10963
10964 $compileNode.empty();
10965
10966 $templateRequest(templateUrl)
10967 .then(function(content) {
10968 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
10969
10970 content = denormalizeTemplate(content);
10971
10972 if (origAsyncDirective.replace) {
10973 if (jqLiteIsTextNode(content)) {
10974 $template = [];
10975 } else {
10976 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
10977 }
10978 compileNode = $template[0];
10979
10980 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
10981 throw $compileMinErr('tplrt',
10982 'Template for directive \'{0}\' must have exactly one root element. {1}',
10983 origAsyncDirective.name, templateUrl);
10984 }
10985
10986 tempTemplateAttrs = {$attr: {}};
10987 replaceWith($rootElement, $compileNode, compileNode);
10988 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
10989
10990 if (isObject(origAsyncDirective.scope)) {
10991 // the original directive that caused the template to be loaded async required
10992 // an isolate scope
10993 markDirectiveScope(templateDirectives, true);
10994 }
10995 directives = templateDirectives.concat(directives);
10996 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
10997 } else {
10998 compileNode = beforeTemplateCompileNode;
10999 $compileNode.html(content);
11000 }
11001
11002 directives.unshift(derivedSyncDirective);
11003
11004 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
11005 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
11006 previousCompileContext);
11007 forEach($rootElement, function(node, i) {
11008 if (node === compileNode) {
11009 $rootElement[i] = $compileNode[0];
11010 }
11011 });
11012 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
11013
11014 while (linkQueue.length) {
11015 var scope = linkQueue.shift(),
11016 beforeTemplateLinkNode = linkQueue.shift(),
11017 linkRootElement = linkQueue.shift(),
11018 boundTranscludeFn = linkQueue.shift(),
11019 linkNode = $compileNode[0];
11020
11021 if (scope.$$destroyed) continue;
11022
11023 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
11024 var oldClasses = beforeTemplateLinkNode.className;
11025
11026 if (!(previousCompileContext.hasElementTranscludeDirective &&
11027 origAsyncDirective.replace)) {
11028 // it was cloned therefore we have to clone as well.
11029 linkNode = jqLiteClone(compileNode);
11030 }
11031 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
11032
11033 // Copy in CSS classes from original node
11034 safeAddClass(jqLite(linkNode), oldClasses);
11035 }
11036 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
11037 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
11038 } else {
11039 childBoundTranscludeFn = boundTranscludeFn;
11040 }
11041 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
11042 childBoundTranscludeFn);
11043 }
11044 linkQueue = null;
11045 }).catch(function(error) {
11046 if (isError(error)) {
11047 $exceptionHandler(error);
11048 }
11049 });
11050
11051 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
11052 var childBoundTranscludeFn = boundTranscludeFn;
11053 if (scope.$$destroyed) return;
11054 if (linkQueue) {
11055 linkQueue.push(scope,
11056 node,
11057 rootElement,
11058 childBoundTranscludeFn);
11059 } else {
11060 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
11061 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
11062 }
11063 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
11064 }
11065 };
11066 }
11067
11068
11069 /**
11070 * Sorting function for bound directives.
11071 */
11072 function byPriority(a, b) {
11073 var diff = b.priority - a.priority;
11074 if (diff !== 0) return diff;
11075 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
11076 return a.index - b.index;
11077 }
11078
11079 function assertNoDuplicate(what, previousDirective, directive, element) {
11080
11081 function wrapModuleNameIfDefined(moduleName) {
11082 return moduleName ?
11083 (' (module: ' + moduleName + ')') :
11084 '';
11085 }
11086
11087 if (previousDirective) {
11088 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
11089 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
11090 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
11091 }
11092 }
11093
11094
11095 function addTextInterpolateDirective(directives, text) {
11096 var interpolateFn = $interpolate(text, true);
11097 if (interpolateFn) {
11098 directives.push({
11099 priority: 0,
11100 compile: function textInterpolateCompileFn(templateNode) {
11101 var templateNodeParent = templateNode.parent(),
11102 hasCompileParent = !!templateNodeParent.length;
11103
11104 // When transcluding a template that has bindings in the root
11105 // we don't have a parent and thus need to add the class during linking fn.
11106 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
11107
11108 return function textInterpolateLinkFn(scope, node) {
11109 var parent = node.parent();
11110 if (!hasCompileParent) compile.$$addBindingClass(parent);
11111 compile.$$addBindingInfo(parent, interpolateFn.expressions);
11112 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
11113 node[0].nodeValue = value;
11114 });
11115 };
11116 }
11117 });
11118 }
11119 }
11120
11121
11122 function wrapTemplate(type, template) {
11123 type = lowercase(type || 'html');
11124 switch (type) {
11125 case 'svg':
11126 case 'math':
11127 var wrapper = window.document.createElement('div');
11128 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
11129 return wrapper.childNodes[0].childNodes;
11130 default:
11131 return template;
11132 }
11133 }
11134
11135
11136 function getTrustedAttrContext(nodeName, attrNormalizedName) {
11137 if (attrNormalizedName === 'srcdoc') {
11138 return $sce.HTML;
11139 }
11140 // All nodes with src attributes require a RESOURCE_URL value, except for
11141 // img and various html5 media nodes, which require the MEDIA_URL context.
11142 if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
11143 if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) {
11144 return $sce.RESOURCE_URL;
11145 }
11146 return $sce.MEDIA_URL;
11147 } else if (attrNormalizedName === 'xlinkHref') {
11148 // Some xlink:href are okay, most aren't
11149 if (nodeName === 'image') return $sce.MEDIA_URL;
11150 if (nodeName === 'a') return $sce.URL;
11151 return $sce.RESOURCE_URL;
11152 } else if (
11153 // Formaction
11154 (nodeName === 'form' && attrNormalizedName === 'action') ||
11155 // If relative URLs can go where they are not expected to, then
11156 // all sorts of trust issues can arise.
11157 (nodeName === 'base' && attrNormalizedName === 'href') ||
11158 // links can be stylesheets or imports, which can run script in the current origin
11159 (nodeName === 'link' && attrNormalizedName === 'href')
11160 ) {
11161 return $sce.RESOURCE_URL;
11162 } else if (nodeName === 'a' && (attrNormalizedName === 'href' ||
11163 attrNormalizedName === 'ngHref')) {
11164 return $sce.URL;
11165 }
11166 }
11167
11168 function getTrustedPropContext(nodeName, propNormalizedName) {
11169 var prop = propNormalizedName.toLowerCase();
11170 return PROP_CONTEXTS[nodeName + '|' + prop] || PROP_CONTEXTS['*|' + prop];
11171 }
11172
11173 function sanitizeSrcsetPropertyValue(value) {
11174 return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset');
11175 }
11176 function addPropertyDirective(node, directives, attrName, propName) {
11177 if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) {
11178 throw $compileMinErr('nodomevents', 'Property bindings for HTML DOM event properties are disallowed');
11179 }
11180
11181 var nodeName = nodeName_(node);
11182 var trustedContext = getTrustedPropContext(nodeName, propName);
11183
11184 var sanitizer = identity;
11185 // Sanitize img[srcset] + source[srcset] values.
11186 if (propName === 'srcset' && (nodeName === 'img' || nodeName === 'source')) {
11187 sanitizer = sanitizeSrcsetPropertyValue;
11188 } else if (trustedContext) {
11189 sanitizer = $sce.getTrusted.bind($sce, trustedContext);
11190 }
11191
11192 directives.push({
11193 priority: 100,
11194 compile: function ngPropCompileFn(_, attr) {
11195 var ngPropGetter = $parse(attr[attrName]);
11196 var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) {
11197 // Unwrap the value to compare the actual inner safe value, not the wrapper object.
11198 return $sce.valueOf(val);
11199 });
11200
11201 return {
11202 pre: function ngPropPreLinkFn(scope, $element) {
11203 function applyPropValue() {
11204 var propValue = ngPropGetter(scope);
11205 $element[0][propName] = sanitizer(propValue);
11206 }
11207
11208 applyPropValue();
11209 scope.$watch(ngPropWatch, applyPropValue);
11210 }
11211 };
11212 }
11213 });
11214 }
11215
11216 function addEventDirective(directives, attrName, eventName) {
11217 directives.push(
11218 createEventDirective($parse, $rootScope, $exceptionHandler, attrName, eventName, /*forceAsync=*/false)
11219 );
11220 }
11221
11222 function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
11223 var nodeName = nodeName_(node);
11224 var trustedContext = getTrustedAttrContext(nodeName, name);
11225 var mustHaveExpression = !isNgAttr;
11226 var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
11227
11228 var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing);
11229
11230 // no interpolation found -> ignore
11231 if (!interpolateFn) return;
11232
11233 if (name === 'multiple' && nodeName === 'select') {
11234 throw $compileMinErr('selmulti',
11235 'Binding to the \'multiple\' attribute is not supported. Element: {0}',
11236 startingTag(node));
11237 }
11238
11239 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
11240 throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed');
11241 }
11242
11243 directives.push({
11244 priority: 100,
11245 compile: function() {
11246 return {
11247 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
11248 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
11249
11250 // If the attribute has changed since last $interpolate()ed
11251 var newValue = attr[name];
11252 if (newValue !== value) {
11253 // we need to interpolate again since the attribute value has been updated
11254 // (e.g. by another directive's compile function)
11255 // ensure unset/empty values make interpolateFn falsy
11256 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
11257 value = newValue;
11258 }
11259
11260 // if attribute was updated so that there is no interpolation going on we don't want to
11261 // register any observers
11262 if (!interpolateFn) return;
11263
11264 // initialize attr object so that it's ready in case we need the value for isolate
11265 // scope initialization, otherwise the value would not be available from isolate
11266 // directive's linking fn during linking phase
11267 attr[name] = interpolateFn(scope);
11268
11269 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
11270 (attr.$$observers && attr.$$observers[name].$$scope || scope).
11271 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
11272 //special case for class attribute addition + removal
11273 //so that class changes can tap into the animation
11274 //hooks provided by the $animate service. Be sure to
11275 //skip animations when the first digest occurs (when
11276 //both the new and the old values are the same) since
11277 //the CSS classes are the non-interpolated values
11278 if (name === 'class' && newValue !== oldValue) {
11279 attr.$updateClass(newValue, oldValue);
11280 } else {
11281 attr.$set(name, newValue);
11282 }
11283 });
11284 }
11285 };
11286 }
11287 });
11288 }
11289
11290
11291 /**
11292 * This is a special jqLite.replaceWith, which can replace items which
11293 * have no parents, provided that the containing jqLite collection is provided.
11294 *
11295 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
11296 * in the root of the tree.
11297 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
11298 * the shell, but replace its DOM node reference.
11299 * @param {Node} newNode The new DOM node.
11300 */
11301 function replaceWith($rootElement, elementsToRemove, newNode) {
11302 var firstElementToRemove = elementsToRemove[0],
11303 removeCount = elementsToRemove.length,
11304 parent = firstElementToRemove.parentNode,
11305 i, ii;
11306
11307 if ($rootElement) {
11308 for (i = 0, ii = $rootElement.length; i < ii; i++) {
11309 if ($rootElement[i] === firstElementToRemove) {
11310 $rootElement[i++] = newNode;
11311 for (var j = i, j2 = j + removeCount - 1,
11312 jj = $rootElement.length;
11313 j < jj; j++, j2++) {
11314 if (j2 < jj) {
11315 $rootElement[j] = $rootElement[j2];
11316 } else {
11317 delete $rootElement[j];
11318 }
11319 }
11320 $rootElement.length -= removeCount - 1;
11321
11322 // If the replaced element is also the jQuery .context then replace it
11323 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
11324 // http://api.jquery.com/context/
11325 if ($rootElement.context === firstElementToRemove) {
11326 $rootElement.context = newNode;
11327 }
11328 break;
11329 }
11330 }
11331 }
11332
11333 if (parent) {
11334 parent.replaceChild(newNode, firstElementToRemove);
11335 }
11336
11337 // Append all the `elementsToRemove` to a fragment. This will...
11338 // - remove them from the DOM
11339 // - allow them to still be traversed with .nextSibling
11340 // - allow a single fragment.qSA to fetch all elements being removed
11341 var fragment = window.document.createDocumentFragment();
11342 for (i = 0; i < removeCount; i++) {
11343 fragment.appendChild(elementsToRemove[i]);
11344 }
11345
11346 if (jqLite.hasData(firstElementToRemove)) {
11347 // Copy over user data (that includes AngularJS's $scope etc.). Don't copy private
11348 // data here because there's no public interface in jQuery to do that and copying over
11349 // event listeners (which is the main use of private data) wouldn't work anyway.
11350 jqLite.data(newNode, jqLite.data(firstElementToRemove));
11351
11352 // Remove $destroy event listeners from `firstElementToRemove`
11353 jqLite(firstElementToRemove).off('$destroy');
11354 }
11355
11356 // Cleanup any data/listeners on the elements and children.
11357 // This includes invoking the $destroy event on any elements with listeners.
11358 jqLite.cleanData(fragment.querySelectorAll('*'));
11359
11360 // Update the jqLite collection to only contain the `newNode`
11361 for (i = 1; i < removeCount; i++) {
11362 delete elementsToRemove[i];
11363 }
11364 elementsToRemove[0] = newNode;
11365 elementsToRemove.length = 1;
11366 }
11367
11368
11369 function cloneAndAnnotateFn(fn, annotation) {
11370 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
11371 }
11372
11373
11374 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
11375 try {
11376 linkFn(scope, $element, attrs, controllers, transcludeFn);
11377 } catch (e) {
11378 $exceptionHandler(e, startingTag($element));
11379 }
11380 }
11381
11382 function strictBindingsCheck(attrName, directiveName) {
11383 if (strictComponentBindingsEnabled) {
11384 throw $compileMinErr('missingattr',
11385 'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!',
11386 attrName, directiveName);
11387 }
11388 }
11389
11390 // Set up $watches for isolate scope and controller bindings.
11391 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
11392 var removeWatchCollection = [];
11393 var initialChanges = {};
11394 var changes;
11395
11396 forEach(bindings, function initializeBinding(definition, scopeName) {
11397 var attrName = definition.attrName,
11398 optional = definition.optional,
11399 mode = definition.mode, // @, =, <, or &
11400 lastValue,
11401 parentGet, parentSet, compare, removeWatch;
11402
11403 switch (mode) {
11404
11405 case '@':
11406 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
11407 strictBindingsCheck(attrName, directive.name);
11408 destination[scopeName] = attrs[attrName] = undefined;
11409
11410 }
11411 removeWatch = attrs.$observe(attrName, function(value) {
11412 if (isString(value) || isBoolean(value)) {
11413 var oldValue = destination[scopeName];
11414 recordChanges(scopeName, value, oldValue);
11415 destination[scopeName] = value;
11416 }
11417 });
11418 attrs.$$observers[attrName].$$scope = scope;
11419 lastValue = attrs[attrName];
11420 if (isString(lastValue)) {
11421 // If the attribute has been provided then we trigger an interpolation to ensure
11422 // the value is there for use in the link fn
11423 destination[scopeName] = $interpolate(lastValue)(scope);
11424 } else if (isBoolean(lastValue)) {
11425 // If the attributes is one of the BOOLEAN_ATTR then AngularJS will have converted
11426 // the value to boolean rather than a string, so we special case this situation
11427 destination[scopeName] = lastValue;
11428 }
11429 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
11430 removeWatchCollection.push(removeWatch);
11431 break;
11432
11433 case '=':
11434 if (!hasOwnProperty.call(attrs, attrName)) {
11435 if (optional) break;
11436 strictBindingsCheck(attrName, directive.name);
11437 attrs[attrName] = undefined;
11438 }
11439 if (optional && !attrs[attrName]) break;
11440
11441 parentGet = $parse(attrs[attrName]);
11442 if (parentGet.literal) {
11443 compare = equals;
11444 } else {
11445 compare = simpleCompare;
11446 }
11447 parentSet = parentGet.assign || function() {
11448 // reset the change, or we will throw this exception on every $digest
11449 lastValue = destination[scopeName] = parentGet(scope);
11450 throw $compileMinErr('nonassign',
11451 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!',
11452 attrs[attrName], attrName, directive.name);
11453 };
11454 lastValue = destination[scopeName] = parentGet(scope);
11455 var parentValueWatch = function parentValueWatch(parentValue) {
11456 if (!compare(parentValue, destination[scopeName])) {
11457 // we are out of sync and need to copy
11458 if (!compare(parentValue, lastValue)) {
11459 // parent changed and it has precedence
11460 destination[scopeName] = parentValue;
11461 } else {
11462 // if the parent can be assigned then do so
11463 parentSet(scope, parentValue = destination[scopeName]);
11464 }
11465 }
11466 lastValue = parentValue;
11467 return lastValue;
11468 };
11469 parentValueWatch.$stateful = true;
11470 if (definition.collection) {
11471 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
11472 } else {
11473 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
11474 }
11475 removeWatchCollection.push(removeWatch);
11476 break;
11477
11478 case '<':
11479 if (!hasOwnProperty.call(attrs, attrName)) {
11480 if (optional) break;
11481 strictBindingsCheck(attrName, directive.name);
11482 attrs[attrName] = undefined;
11483 }
11484 if (optional && !attrs[attrName]) break;
11485
11486 parentGet = $parse(attrs[attrName]);
11487 var isLiteral = parentGet.literal;
11488
11489 var initialValue = destination[scopeName] = parentGet(scope);
11490 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
11491
11492 removeWatch = scope[definition.collection ? '$watchCollection' : '$watch'](parentGet, function parentValueWatchAction(newValue, oldValue) {
11493 if (oldValue === newValue) {
11494 if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) {
11495 return;
11496 }
11497 oldValue = initialValue;
11498 }
11499 recordChanges(scopeName, newValue, oldValue);
11500 destination[scopeName] = newValue;
11501 });
11502
11503 removeWatchCollection.push(removeWatch);
11504 break;
11505
11506 case '&':
11507 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
11508 strictBindingsCheck(attrName, directive.name);
11509 }
11510 // Don't assign Object.prototype method to scope
11511 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
11512
11513 // Don't assign noop to destination if expression is not valid
11514 if (parentGet === noop && optional) break;
11515
11516 destination[scopeName] = function(locals) {
11517 return parentGet(scope, locals);
11518 };
11519 break;
11520 }
11521 });
11522
11523 function recordChanges(key, currentValue, previousValue) {
11524 if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) {
11525 // If we have not already scheduled the top level onChangesQueue handler then do so now
11526 if (!onChangesQueue) {
11527 scope.$$postDigest(flushOnChangesQueue);
11528 onChangesQueue = [];
11529 }
11530 // If we have not already queued a trigger of onChanges for this controller then do so now
11531 if (!changes) {
11532 changes = {};
11533 onChangesQueue.push(triggerOnChangesHook);
11534 }
11535 // If the has been a change on this property already then we need to reuse the previous value
11536 if (changes[key]) {
11537 previousValue = changes[key].previousValue;
11538 }
11539 // Store this change
11540 changes[key] = new SimpleChange(previousValue, currentValue);
11541 }
11542 }
11543
11544 function triggerOnChangesHook() {
11545 destination.$onChanges(changes);
11546 // Now clear the changes so that we schedule onChanges when more changes arrive
11547 changes = undefined;
11548 }
11549
11550 return {
11551 initialChanges: initialChanges,
11552 removeWatches: removeWatchCollection.length && function removeWatches() {
11553 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
11554 removeWatchCollection[i]();
11555 }
11556 }
11557 };
11558 }
11559 }];
11560}
11561
11562function SimpleChange(previous, current) {
11563 this.previousValue = previous;
11564 this.currentValue = current;
11565}
11566SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; };
11567
11568
11569var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i;
11570var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g;
11571
11572/**
11573 * Converts all accepted directives format into proper directive name.
11574 * @param name Name to normalize
11575 */
11576function directiveNormalize(name) {
11577 return name
11578 .replace(PREFIX_REGEXP, '')
11579 .replace(SPECIAL_CHARS_REGEXP, function(_, letter, offset) {
11580 return offset ? letter.toUpperCase() : letter;
11581 });
11582}
11583
11584/**
11585 * @ngdoc type
11586 * @name $compile.directive.Attributes
11587 *
11588 * @description
11589 * A shared object between directive compile / linking functions which contains normalized DOM
11590 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
11591 * needed since all of these are treated as equivalent in AngularJS:
11592 *
11593 * ```
11594 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
11595 * ```
11596 */
11597
11598/**
11599 * @ngdoc property
11600 * @name $compile.directive.Attributes#$attr
11601 *
11602 * @description
11603 * A map of DOM element attribute names to the normalized name. This is
11604 * needed to do reverse lookup from normalized name back to actual name.
11605 */
11606
11607
11608/**
11609 * @ngdoc method
11610 * @name $compile.directive.Attributes#$set
11611 * @kind function
11612 *
11613 * @description
11614 * Set DOM element attribute value.
11615 *
11616 *
11617 * @param {string} name Normalized element attribute name of the property to modify. The name is
11618 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
11619 * property to the original name.
11620 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
11621 */
11622
11623
11624
11625/**
11626 * Closure compiler type information
11627 */
11628
11629function nodesetLinkingFn(
11630 /* angular.Scope */ scope,
11631 /* NodeList */ nodeList,
11632 /* Element */ rootElement,
11633 /* function(Function) */ boundTranscludeFn
11634) {}
11635
11636function directiveLinkingFn(
11637 /* nodesetLinkingFn */ nodesetLinkingFn,
11638 /* angular.Scope */ scope,
11639 /* Node */ node,
11640 /* Element */ rootElement,
11641 /* function(Function) */ boundTranscludeFn
11642) {}
11643
11644function tokenDifference(str1, str2) {
11645 var values = '',
11646 tokens1 = str1.split(/\s+/),
11647 tokens2 = str2.split(/\s+/);
11648
11649 outer:
11650 for (var i = 0; i < tokens1.length; i++) {
11651 var token = tokens1[i];
11652 for (var j = 0; j < tokens2.length; j++) {
11653 if (token === tokens2[j]) continue outer;
11654 }
11655 values += (values.length > 0 ? ' ' : '') + token;
11656 }
11657 return values;
11658}
11659
11660function removeComments(jqNodes) {
11661 jqNodes = jqLite(jqNodes);
11662 var i = jqNodes.length;
11663
11664 if (i <= 1) {
11665 return jqNodes;
11666 }
11667
11668 while (i--) {
11669 var node = jqNodes[i];
11670 if (node.nodeType === NODE_TYPE_COMMENT ||
11671 (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) {
11672 splice.call(jqNodes, i, 1);
11673 }
11674 }
11675 return jqNodes;
11676}
11677
11678var $controllerMinErr = minErr('$controller');
11679
11680
11681var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;
11682function identifierForController(controller, ident) {
11683 if (ident && isString(ident)) return ident;
11684 if (isString(controller)) {
11685 var match = CNTRL_REG.exec(controller);
11686 if (match) return match[3];
11687 }
11688}
11689
11690
11691/**
11692 * @ngdoc provider
11693 * @name $controllerProvider
11694 * @this
11695 *
11696 * @description
11697 * The {@link ng.$controller $controller service} is used by AngularJS to create new
11698 * controllers.
11699 *
11700 * This provider allows controller registration via the
11701 * {@link ng.$controllerProvider#register register} method.
11702 */
11703function $ControllerProvider() {
11704 var controllers = {};
11705
11706 /**
11707 * @ngdoc method
11708 * @name $controllerProvider#has
11709 * @param {string} name Controller name to check.
11710 */
11711 this.has = function(name) {
11712 return controllers.hasOwnProperty(name);
11713 };
11714
11715 /**
11716 * @ngdoc method
11717 * @name $controllerProvider#register
11718 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
11719 * the names and the values are the constructors.
11720 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
11721 * annotations in the array notation).
11722 */
11723 this.register = function(name, constructor) {
11724 assertNotHasOwnProperty(name, 'controller');
11725 if (isObject(name)) {
11726 extend(controllers, name);
11727 } else {
11728 controllers[name] = constructor;
11729 }
11730 };
11731
11732 this.$get = ['$injector', function($injector) {
11733
11734 /**
11735 * @ngdoc service
11736 * @name $controller
11737 * @requires $injector
11738 *
11739 * @param {Function|string} constructor If called with a function then it's considered to be the
11740 * controller constructor function. Otherwise it's considered to be a string which is used
11741 * to retrieve the controller constructor using the following steps:
11742 *
11743 * * check if a controller with given name is registered via `$controllerProvider`
11744 * * check if evaluating the string on the current scope returns a constructor
11745 *
11746 * The string can use the `controller as property` syntax, where the controller instance is published
11747 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
11748 * to work correctly.
11749 *
11750 * @param {Object} locals Injection locals for Controller.
11751 * @return {Object} Instance of given controller.
11752 *
11753 * @description
11754 * `$controller` service is responsible for instantiating controllers.
11755 *
11756 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
11757 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
11758 */
11759 return function $controller(expression, locals, later, ident) {
11760 // PRIVATE API:
11761 // param `later` --- indicates that the controller's constructor is invoked at a later time.
11762 // If true, $controller will allocate the object with the correct
11763 // prototype chain, but will not invoke the controller until a returned
11764 // callback is invoked.
11765 // param `ident` --- An optional label which overrides the label parsed from the controller
11766 // expression, if any.
11767 var instance, match, constructor, identifier;
11768 later = later === true;
11769 if (ident && isString(ident)) {
11770 identifier = ident;
11771 }
11772
11773 if (isString(expression)) {
11774 match = expression.match(CNTRL_REG);
11775 if (!match) {
11776 throw $controllerMinErr('ctrlfmt',
11777 'Badly formed controller string \'{0}\'. ' +
11778 'Must match `__name__ as __id__` or `__name__`.', expression);
11779 }
11780 constructor = match[1];
11781 identifier = identifier || match[3];
11782 expression = controllers.hasOwnProperty(constructor)
11783 ? controllers[constructor]
11784 : getter(locals.$scope, constructor, true);
11785
11786 if (!expression) {
11787 throw $controllerMinErr('ctrlreg',
11788 'The controller with the name \'{0}\' is not registered.', constructor);
11789 }
11790
11791 assertArgFn(expression, constructor, true);
11792 }
11793
11794 if (later) {
11795 // Instantiate controller later:
11796 // This machinery is used to create an instance of the object before calling the
11797 // controller's constructor itself.
11798 //
11799 // This allows properties to be added to the controller before the constructor is
11800 // invoked. Primarily, this is used for isolate scope bindings in $compile.
11801 //
11802 // This feature is not intended for use by applications, and is thus not documented
11803 // publicly.
11804 // Object creation: http://jsperf.com/create-constructor/2
11805 var controllerPrototype = (isArray(expression) ?
11806 expression[expression.length - 1] : expression).prototype;
11807 instance = Object.create(controllerPrototype || null);
11808
11809 if (identifier) {
11810 addIdentifier(locals, identifier, instance, constructor || expression.name);
11811 }
11812
11813 return extend(function $controllerInit() {
11814 var result = $injector.invoke(expression, instance, locals, constructor);
11815 if (result !== instance && (isObject(result) || isFunction(result))) {
11816 instance = result;
11817 if (identifier) {
11818 // If result changed, re-assign controllerAs value to scope.
11819 addIdentifier(locals, identifier, instance, constructor || expression.name);
11820 }
11821 }
11822 return instance;
11823 }, {
11824 instance: instance,
11825 identifier: identifier
11826 });
11827 }
11828
11829 instance = $injector.instantiate(expression, locals, constructor);
11830
11831 if (identifier) {
11832 addIdentifier(locals, identifier, instance, constructor || expression.name);
11833 }
11834
11835 return instance;
11836 };
11837
11838 function addIdentifier(locals, identifier, instance, name) {
11839 if (!(locals && isObject(locals.$scope))) {
11840 throw minErr('$controller')('noscp',
11841 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.',
11842 name, identifier);
11843 }
11844
11845 locals.$scope[identifier] = instance;
11846 }
11847 }];
11848}
11849
11850/**
11851 * @ngdoc service
11852 * @name $document
11853 * @requires $window
11854 * @this
11855 *
11856 * @description
11857 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
11858 *
11859 * @example
11860 <example module="documentExample" name="document">
11861 <file name="index.html">
11862 <div ng-controller="ExampleController">
11863 <p>$document title: <b ng-bind="title"></b></p>
11864 <p>window.document title: <b ng-bind="windowTitle"></b></p>
11865 </div>
11866 </file>
11867 <file name="script.js">
11868 angular.module('documentExample', [])
11869 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
11870 $scope.title = $document[0].title;
11871 $scope.windowTitle = angular.element(window.document)[0].title;
11872 }]);
11873 </file>
11874 </example>
11875 */
11876function $DocumentProvider() {
11877 this.$get = ['$window', function(window) {
11878 return jqLite(window.document);
11879 }];
11880}
11881
11882
11883/**
11884 * @private
11885 * @this
11886 * Listens for document visibility change and makes the current status accessible.
11887 */
11888function $$IsDocumentHiddenProvider() {
11889 this.$get = ['$document', '$rootScope', function($document, $rootScope) {
11890 var doc = $document[0];
11891 var hidden = doc && doc.hidden;
11892
11893 $document.on('visibilitychange', changeListener);
11894
11895 $rootScope.$on('$destroy', function() {
11896 $document.off('visibilitychange', changeListener);
11897 });
11898
11899 function changeListener() {
11900 hidden = doc.hidden;
11901 }
11902
11903 return function() {
11904 return hidden;
11905 };
11906 }];
11907}
11908
11909/**
11910 * @ngdoc service
11911 * @name $exceptionHandler
11912 * @requires ng.$log
11913 * @this
11914 *
11915 * @description
11916 * Any uncaught exception in AngularJS expressions is delegated to this service.
11917 * The default implementation simply delegates to `$log.error` which logs it into
11918 * the browser console.
11919 *
11920 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
11921 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
11922 *
11923 * ## Example:
11924 *
11925 * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught
11926 * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead
11927 * of `$log.error()`.
11928 *
11929 * ```js
11930 * angular.
11931 * module('exceptionOverwrite', []).
11932 * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
11933 * return function myExceptionHandler(exception, cause) {
11934 * logErrorsToBackend(exception, cause);
11935 * $log.warn(exception, cause);
11936 * };
11937 * }]);
11938 * ```
11939 *
11940 * <hr />
11941 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
11942 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
11943 * (unless executed during a digest).
11944 *
11945 * If you wish, you can manually delegate exceptions, e.g.
11946 * `try { ... } catch(e) { $exceptionHandler(e); }`
11947 *
11948 * @param {Error} exception Exception associated with the error.
11949 * @param {string=} cause Optional information about the context in which
11950 * the error was thrown.
11951 *
11952 */
11953function $ExceptionHandlerProvider() {
11954 this.$get = ['$log', function($log) {
11955 return function(exception, cause) {
11956 $log.error.apply($log, arguments);
11957 };
11958 }];
11959}
11960
11961var $$ForceReflowProvider = /** @this */ function() {
11962 this.$get = ['$document', function($document) {
11963 return function(domNode) {
11964 //the line below will force the browser to perform a repaint so
11965 //that all the animated elements within the animation frame will
11966 //be properly updated and drawn on screen. This is required to
11967 //ensure that the preparation animation is properly flushed so that
11968 //the active state picks up from there. DO NOT REMOVE THIS LINE.
11969 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
11970 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
11971 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
11972 if (domNode) {
11973 if (!domNode.nodeType && domNode instanceof jqLite) {
11974 domNode = domNode[0];
11975 }
11976 } else {
11977 domNode = $document[0].body;
11978 }
11979 return domNode.offsetWidth + 1;
11980 };
11981 }];
11982};
11983
11984var APPLICATION_JSON = 'application/json';
11985var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
11986var JSON_START = /^\[|^\{(?!\{)/;
11987var JSON_ENDS = {
11988 '[': /]$/,
11989 '{': /}$/
11990};
11991var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/;
11992var $httpMinErr = minErr('$http');
11993
11994function serializeValue(v) {
11995 if (isObject(v)) {
11996 return isDate(v) ? v.toISOString() : toJson(v);
11997 }
11998 return v;
11999}
12000
12001
12002/** @this */
12003function $HttpParamSerializerProvider() {
12004 /**
12005 * @ngdoc service
12006 * @name $httpParamSerializer
12007 * @description
12008 *
12009 * Default {@link $http `$http`} params serializer that converts objects to strings
12010 * according to the following rules:
12011 *
12012 * * `{'foo': 'bar'}` results in `foo=bar`
12013 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
12014 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
12015 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
12016 *
12017 * Note that serializer will sort the request parameters alphabetically.
12018 */
12019
12020 this.$get = function() {
12021 return function ngParamSerializer(params) {
12022 if (!params) return '';
12023 var parts = [];
12024 forEachSorted(params, function(value, key) {
12025 if (value === null || isUndefined(value) || isFunction(value)) return;
12026 if (isArray(value)) {
12027 forEach(value, function(v) {
12028 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
12029 });
12030 } else {
12031 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
12032 }
12033 });
12034
12035 return parts.join('&');
12036 };
12037 };
12038}
12039
12040/** @this */
12041function $HttpParamSerializerJQLikeProvider() {
12042 /**
12043 * @ngdoc service
12044 * @name $httpParamSerializerJQLike
12045 *
12046 * @description
12047 *
12048 * Alternative {@link $http `$http`} params serializer that follows
12049 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
12050 * The serializer will also sort the params alphabetically.
12051 *
12052 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
12053 *
12054 * ```js
12055 * $http({
12056 * url: myUrl,
12057 * method: 'GET',
12058 * params: myParams,
12059 * paramSerializer: '$httpParamSerializerJQLike'
12060 * });
12061 * ```
12062 *
12063 * It is also possible to set it as the default `paramSerializer` in the
12064 * {@link $httpProvider#defaults `$httpProvider`}.
12065 *
12066 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
12067 * form data for submission:
12068 *
12069 * ```js
12070 * .controller(function($http, $httpParamSerializerJQLike) {
12071 * //...
12072 *
12073 * $http({
12074 * url: myUrl,
12075 * method: 'POST',
12076 * data: $httpParamSerializerJQLike(myData),
12077 * headers: {
12078 * 'Content-Type': 'application/x-www-form-urlencoded'
12079 * }
12080 * });
12081 *
12082 * });
12083 * ```
12084 *
12085 */
12086 this.$get = function() {
12087 return function jQueryLikeParamSerializer(params) {
12088 if (!params) return '';
12089 var parts = [];
12090 serialize(params, '', true);
12091 return parts.join('&');
12092
12093 function serialize(toSerialize, prefix, topLevel) {
12094 if (isArray(toSerialize)) {
12095 forEach(toSerialize, function(value, index) {
12096 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
12097 });
12098 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
12099 forEachSorted(toSerialize, function(value, key) {
12100 serialize(value, prefix +
12101 (topLevel ? '' : '[') +
12102 key +
12103 (topLevel ? '' : ']'));
12104 });
12105 } else {
12106 if (isFunction(toSerialize)) {
12107 toSerialize = toSerialize();
12108 }
12109 parts.push(encodeUriQuery(prefix) + '=' +
12110 (toSerialize == null ? '' : encodeUriQuery(serializeValue(toSerialize))));
12111 }
12112 }
12113 };
12114 };
12115}
12116
12117function defaultHttpResponseTransform(data, headers) {
12118 if (isString(data)) {
12119 // Strip json vulnerability protection prefix and trim whitespace
12120 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
12121
12122 if (tempData) {
12123 var contentType = headers('Content-Type');
12124 var hasJsonContentType = contentType && (contentType.indexOf(APPLICATION_JSON) === 0);
12125
12126 if (hasJsonContentType || isJsonLike(tempData)) {
12127 try {
12128 data = fromJson(tempData);
12129 } catch (e) {
12130 if (!hasJsonContentType) {
12131 return data;
12132 }
12133 throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' +
12134 'Parse error: "{1}"', data, e);
12135 }
12136 }
12137 }
12138 }
12139
12140 return data;
12141}
12142
12143function isJsonLike(str) {
12144 var jsonStart = str.match(JSON_START);
12145 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
12146}
12147
12148/**
12149 * Parse headers into key value object
12150 *
12151 * @param {string} headers Raw headers as a string
12152 * @returns {Object} Parsed headers as key value object
12153 */
12154function parseHeaders(headers) {
12155 var parsed = createMap(), i;
12156
12157 function fillInParsed(key, val) {
12158 if (key) {
12159 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
12160 }
12161 }
12162
12163 if (isString(headers)) {
12164 forEach(headers.split('\n'), function(line) {
12165 i = line.indexOf(':');
12166 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
12167 });
12168 } else if (isObject(headers)) {
12169 forEach(headers, function(headerVal, headerKey) {
12170 fillInParsed(lowercase(headerKey), trim(headerVal));
12171 });
12172 }
12173
12174 return parsed;
12175}
12176
12177
12178/**
12179 * Returns a function that provides access to parsed headers.
12180 *
12181 * Headers are lazy parsed when first requested.
12182 * @see parseHeaders
12183 *
12184 * @param {(string|Object)} headers Headers to provide access to.
12185 * @returns {function(string=)} Returns a getter function which if called with:
12186 *
12187 * - if called with an argument returns a single header value or null
12188 * - if called with no arguments returns an object containing all headers.
12189 */
12190function headersGetter(headers) {
12191 var headersObj;
12192
12193 return function(name) {
12194 if (!headersObj) headersObj = parseHeaders(headers);
12195
12196 if (name) {
12197 var value = headersObj[lowercase(name)];
12198 if (value === undefined) {
12199 value = null;
12200 }
12201 return value;
12202 }
12203
12204 return headersObj;
12205 };
12206}
12207
12208
12209/**
12210 * Chain all given functions
12211 *
12212 * This function is used for both request and response transforming
12213 *
12214 * @param {*} data Data to transform.
12215 * @param {function(string=)} headers HTTP headers getter fn.
12216 * @param {number} status HTTP status code of the response.
12217 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
12218 * @returns {*} Transformed data.
12219 */
12220function transformData(data, headers, status, fns) {
12221 if (isFunction(fns)) {
12222 return fns(data, headers, status);
12223 }
12224
12225 forEach(fns, function(fn) {
12226 data = fn(data, headers, status);
12227 });
12228
12229 return data;
12230}
12231
12232
12233function isSuccess(status) {
12234 return 200 <= status && status < 300;
12235}
12236
12237
12238/**
12239 * @ngdoc provider
12240 * @name $httpProvider
12241 * @this
12242 *
12243 * @description
12244 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
12245 */
12246function $HttpProvider() {
12247 /**
12248 * @ngdoc property
12249 * @name $httpProvider#defaults
12250 * @description
12251 *
12252 * Object containing default values for all {@link ng.$http $http} requests.
12253 *
12254 * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with
12255 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses
12256 * by default. See {@link $http#caching $http Caching} for more information.
12257 *
12258 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
12259 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
12260 * setting default headers.
12261 * - **`defaults.headers.common`**
12262 * - **`defaults.headers.post`**
12263 * - **`defaults.headers.put`**
12264 * - **`defaults.headers.patch`**
12265 *
12266 * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the
12267 * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the
12268 * {@link $jsonpCallbacks} service. Defaults to `'callback'`.
12269 *
12270 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
12271 * used to the prepare string representation of request parameters (specified as an object).
12272 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
12273 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
12274 *
12275 * - **`defaults.transformRequest`** -
12276 * `{Array<function(data, headersGetter)>|function(data, headersGetter)}` -
12277 * An array of functions (or a single function) which are applied to the request data.
12278 * By default, this is an array with one request transformation function:
12279 *
12280 * - If the `data` property of the request configuration object contains an object, serialize it
12281 * into JSON format.
12282 *
12283 * - **`defaults.transformResponse`** -
12284 * `{Array<function(data, headersGetter, status)>|function(data, headersGetter, status)}` -
12285 * An array of functions (or a single function) which are applied to the response data. By default,
12286 * this is an array which applies one response transformation function that does two things:
12287 *
12288 * - If XSRF prefix is detected, strip it
12289 * (see {@link ng.$http#security-considerations Security Considerations in the $http docs}).
12290 * - If the `Content-Type` is `application/json` or the response looks like JSON,
12291 * deserialize it using a JSON parser.
12292 *
12293 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
12294 * Defaults value is `'XSRF-TOKEN'`.
12295 *
12296 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
12297 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
12298 *
12299 */
12300 var defaults = this.defaults = {
12301 // transform incoming response data
12302 transformResponse: [defaultHttpResponseTransform],
12303
12304 // transform outgoing request data
12305 transformRequest: [function(d) {
12306 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
12307 }],
12308
12309 // default headers
12310 headers: {
12311 common: {
12312 'Accept': 'application/json, text/plain, */*'
12313 },
12314 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
12315 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
12316 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
12317 },
12318
12319 xsrfCookieName: 'XSRF-TOKEN',
12320 xsrfHeaderName: 'X-XSRF-TOKEN',
12321
12322 paramSerializer: '$httpParamSerializer',
12323
12324 jsonpCallbackParam: 'callback'
12325 };
12326
12327 var useApplyAsync = false;
12328 /**
12329 * @ngdoc method
12330 * @name $httpProvider#useApplyAsync
12331 * @description
12332 *
12333 * Configure $http service to combine processing of multiple http responses received at around
12334 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
12335 * significant performance improvement for bigger applications that make many HTTP requests
12336 * concurrently (common during application bootstrap).
12337 *
12338 * Defaults to false. If no value is specified, returns the current configured value.
12339 *
12340 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
12341 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
12342 * to load and share the same digest cycle.
12343 *
12344 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
12345 * otherwise, returns the current configured value.
12346 */
12347 this.useApplyAsync = function(value) {
12348 if (isDefined(value)) {
12349 useApplyAsync = !!value;
12350 return this;
12351 }
12352 return useApplyAsync;
12353 };
12354
12355 /**
12356 * @ngdoc property
12357 * @name $httpProvider#interceptors
12358 * @description
12359 *
12360 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
12361 * pre-processing of request or postprocessing of responses.
12362 *
12363 * These service factories are ordered by request, i.e. they are applied in the same order as the
12364 * array, on request, but reverse order, on response.
12365 *
12366 * {@link ng.$http#interceptors Interceptors detailed info}
12367 */
12368 var interceptorFactories = this.interceptors = [];
12369
12370 /**
12371 * @ngdoc property
12372 * @name $httpProvider#xsrfTrustedOrigins
12373 * @description
12374 *
12375 * Array containing URLs whose origins are trusted to receive the XSRF token. See the
12376 * {@link ng.$http#security-considerations Security Considerations} sections for more details on
12377 * XSRF.
12378 *
12379 * **Note:** An "origin" consists of the [URI scheme](https://en.wikipedia.org/wiki/URI_scheme),
12380 * the [hostname](https://en.wikipedia.org/wiki/Hostname) and the
12381 * [port number](https://en.wikipedia.org/wiki/Port_(computer_networking). For `http:` and
12382 * `https:`, the port number can be omitted if using th default ports (80 and 443 respectively).
12383 * Examples: `http://example.com`, `https://api.example.com:9876`
12384 *
12385 * <div class="alert alert-warning">
12386 * It is not possible to trust specific URLs/paths. The `path`, `query` and `fragment` parts
12387 * of a URL will be ignored. For example, `https://foo.com/path/bar?query=baz#fragment` will be
12388 * treated as `https://foo.com`, meaning that **all** requests to URLs starting with
12389 * `https://foo.com/` will include the XSRF token.
12390 * </div>
12391 *
12392 * @example
12393 *
12394 * ```js
12395 * // App served from `https://example.com/`.
12396 * angular.
12397 * module('xsrfTrustedOriginsExample', []).
12398 * config(['$httpProvider', function($httpProvider) {
12399 * $httpProvider.xsrfTrustedOrigins.push('https://api.example.com');
12400 * }]).
12401 * run(['$http', function($http) {
12402 * // The XSRF token will be sent.
12403 * $http.get('https://api.example.com/preferences').then(...);
12404 *
12405 * // The XSRF token will NOT be sent.
12406 * $http.get('https://stats.example.com/activity').then(...);
12407 * }]);
12408 * ```
12409 */
12410 var xsrfTrustedOrigins = this.xsrfTrustedOrigins = [];
12411
12412 /**
12413 * @ngdoc property
12414 * @name $httpProvider#xsrfWhitelistedOrigins
12415 * @description
12416 *
12417 * @deprecated
12418 * sinceVersion="1.8.1"
12419 *
12420 * This property is deprecated. Use {@link $httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins}
12421 * instead.
12422 */
12423 Object.defineProperty(this, 'xsrfWhitelistedOrigins', {
12424 get: function() {
12425 return this.xsrfTrustedOrigins;
12426 },
12427 set: function(origins) {
12428 this.xsrfTrustedOrigins = origins;
12429 }
12430 });
12431
12432 this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce',
12433 function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) {
12434
12435 var defaultCache = $cacheFactory('$http');
12436
12437 /**
12438 * Make sure that default param serializer is exposed as a function
12439 */
12440 defaults.paramSerializer = isString(defaults.paramSerializer) ?
12441 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
12442
12443 /**
12444 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
12445 * The reversal is needed so that we can build up the interception chain around the
12446 * server request.
12447 */
12448 var reversedInterceptors = [];
12449
12450 forEach(interceptorFactories, function(interceptorFactory) {
12451 reversedInterceptors.unshift(isString(interceptorFactory)
12452 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
12453 });
12454
12455 /**
12456 * A function to check request URLs against a list of allowed origins.
12457 */
12458 var urlIsAllowedOrigin = urlIsAllowedOriginFactory(xsrfTrustedOrigins);
12459
12460 /**
12461 * @ngdoc service
12462 * @kind function
12463 * @name $http
12464 * @requires ng.$httpBackend
12465 * @requires $cacheFactory
12466 * @requires $rootScope
12467 * @requires $q
12468 * @requires $injector
12469 *
12470 * @description
12471 * The `$http` service is a core AngularJS service that facilitates communication with the remote
12472 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
12473 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
12474 *
12475 * For unit testing applications that use `$http` service, see
12476 * {@link ngMock.$httpBackend $httpBackend mock}.
12477 *
12478 * For a higher level of abstraction, please check out the {@link ngResource.$resource
12479 * $resource} service.
12480 *
12481 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
12482 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
12483 * it is important to familiarize yourself with these APIs and the guarantees they provide.
12484 *
12485 *
12486 * ## General usage
12487 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
12488 * that is used to generate an HTTP request and returns a {@link ng.$q promise} that is
12489 * resolved (request success) or rejected (request failure) with a
12490 * {@link ng.$http#$http-returns response} object.
12491 *
12492 * ```js
12493 * // Simple GET request example:
12494 * $http({
12495 * method: 'GET',
12496 * url: '/someUrl'
12497 * }).then(function successCallback(response) {
12498 * // this callback will be called asynchronously
12499 * // when the response is available
12500 * }, function errorCallback(response) {
12501 * // called asynchronously if an error occurs
12502 * // or server returns response with an error status.
12503 * });
12504 * ```
12505 *
12506 *
12507 * ## Shortcut methods
12508 *
12509 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
12510 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
12511 * last argument.
12512 *
12513 * ```js
12514 * $http.get('/someUrl', config).then(successCallback, errorCallback);
12515 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
12516 * ```
12517 *
12518 * Complete list of shortcut methods:
12519 *
12520 * - {@link ng.$http#get $http.get}
12521 * - {@link ng.$http#head $http.head}
12522 * - {@link ng.$http#post $http.post}
12523 * - {@link ng.$http#put $http.put}
12524 * - {@link ng.$http#delete $http.delete}
12525 * - {@link ng.$http#jsonp $http.jsonp}
12526 * - {@link ng.$http#patch $http.patch}
12527 *
12528 *
12529 * ## Writing Unit Tests that use $http
12530 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
12531 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
12532 * request using trained responses.
12533 *
12534 * ```
12535 * $httpBackend.expectGET(...);
12536 * $http.get(...);
12537 * $httpBackend.flush();
12538 * ```
12539 *
12540 * ## Setting HTTP Headers
12541 *
12542 * The $http service will automatically add certain HTTP headers to all requests. These defaults
12543 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
12544 * object, which currently contains this default configuration:
12545 *
12546 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
12547 * - <code>Accept: application/json, text/plain, \*&#65279;/&#65279;\*</code>
12548 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
12549 * - `Content-Type: application/json`
12550 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
12551 * - `Content-Type: application/json`
12552 *
12553 * To add or overwrite these defaults, simply add or remove a property from these configuration
12554 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
12555 * with the lowercased HTTP method name as the key, e.g.
12556 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
12557 *
12558 * The defaults can also be set at runtime via the `$http.defaults` object in the same
12559 * fashion. For example:
12560 *
12561 * ```
12562 * module.run(function($http) {
12563 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
12564 * });
12565 * ```
12566 *
12567 * In addition, you can supply a `headers` property in the config object passed when
12568 * calling `$http(config)`, which overrides the defaults without changing them globally.
12569 *
12570 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
12571 * Use the `headers` property, setting the desired header to `undefined`. For example:
12572 *
12573 * ```js
12574 * var req = {
12575 * method: 'POST',
12576 * url: 'http://example.com',
12577 * headers: {
12578 * 'Content-Type': undefined
12579 * },
12580 * data: { test: 'test' }
12581 * }
12582 *
12583 * $http(req).then(function(){...}, function(){...});
12584 * ```
12585 *
12586 * ## Transforming Requests and Responses
12587 *
12588 * Both requests and responses can be transformed using transformation functions: `transformRequest`
12589 * and `transformResponse`. These properties can be a single function that returns
12590 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
12591 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
12592 *
12593 * <div class="alert alert-warning">
12594 * **Note:** AngularJS does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline.
12595 * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference).
12596 * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest
12597 * function will be reflected on the scope and in any templates where the object is data-bound.
12598 * To prevent this, transform functions should have no side-effects.
12599 * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return.
12600 * </div>
12601 *
12602 * ### Default Transformations
12603 *
12604 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
12605 * `defaults.transformResponse` properties. If a request does not provide its own transformations
12606 * then these will be applied.
12607 *
12608 * You can augment or replace the default transformations by modifying these properties by adding to or
12609 * replacing the array.
12610 *
12611 * AngularJS provides the following default transformations:
12612 *
12613 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`) is
12614 * an array with one function that does the following:
12615 *
12616 * - If the `data` property of the request configuration object contains an object, serialize it
12617 * into JSON format.
12618 *
12619 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`) is
12620 * an array with one function that does the following:
12621 *
12622 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
12623 * - If the `Content-Type` is `application/json` or the response looks like JSON,
12624 * deserialize it using a JSON parser.
12625 *
12626 *
12627 * ### Overriding the Default Transformations Per Request
12628 *
12629 * If you wish to override the request/response transformations only for a single request then provide
12630 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
12631 * into `$http`.
12632 *
12633 * Note that if you provide these properties on the config object the default transformations will be
12634 * overwritten. If you wish to augment the default transformations then you must include them in your
12635 * local transformation array.
12636 *
12637 * The following code demonstrates adding a new response transformation to be run after the default response
12638 * transformations have been run.
12639 *
12640 * ```js
12641 * function appendTransform(defaults, transform) {
12642 *
12643 * // We can't guarantee that the default transformation is an array
12644 * defaults = angular.isArray(defaults) ? defaults : [defaults];
12645 *
12646 * // Append the new transformation to the defaults
12647 * return defaults.concat(transform);
12648 * }
12649 *
12650 * $http({
12651 * url: '...',
12652 * method: 'GET',
12653 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
12654 * return doTransform(value);
12655 * })
12656 * });
12657 * ```
12658 *
12659 *
12660 * ## Caching
12661 *
12662 * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must
12663 * set the config.cache value or the default cache value to TRUE or to a cache object (created
12664 * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes
12665 * precedence over the default cache value.
12666 *
12667 * In order to:
12668 * * cache all responses - set the default cache value to TRUE or to a cache object
12669 * * cache a specific response - set config.cache value to TRUE or to a cache object
12670 *
12671 * If caching is enabled, but neither the default cache nor config.cache are set to a cache object,
12672 * then the default `$cacheFactory("$http")` object is used.
12673 *
12674 * The default cache value can be set by updating the
12675 * {@link ng.$http#defaults `$http.defaults.cache`} property or the
12676 * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property.
12677 *
12678 * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using
12679 * the relevant cache object. The next time the same request is made, the response is returned
12680 * from the cache without sending a request to the server.
12681 *
12682 * Take note that:
12683 *
12684 * * Only GET and JSONP requests are cached.
12685 * * The cache key is the request URL including search parameters; headers are not considered.
12686 * * Cached responses are returned asynchronously, in the same way as responses from the server.
12687 * * If multiple identical requests are made using the same cache, which is not yet populated,
12688 * one request will be made to the server and remaining requests will return the same response.
12689 * * A cache-control header on the response does not affect if or how responses are cached.
12690 *
12691 *
12692 * ## Interceptors
12693 *
12694 * Before you start creating interceptors, be sure to understand the
12695 * {@link ng.$q $q and deferred/promise APIs}.
12696 *
12697 * For purposes of global error handling, authentication, or any kind of synchronous or
12698 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
12699 * able to intercept requests before they are handed to the server and
12700 * responses before they are handed over to the application code that
12701 * initiated these requests. The interceptors leverage the {@link ng.$q
12702 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
12703 *
12704 * The interceptors are service factories that are registered with the `$httpProvider` by
12705 * adding them to the `$httpProvider.interceptors` array. The factory is called and
12706 * injected with dependencies (if specified) and returns the interceptor.
12707 *
12708 * There are two kinds of interceptors (and two kinds of rejection interceptors):
12709 *
12710 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
12711 * modify the `config` object or create a new one. The function needs to return the `config`
12712 * object directly, or a promise containing the `config` or a new `config` object.
12713 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
12714 * resolved with a rejection.
12715 * * `response`: interceptors get called with http `response` object. The function is free to
12716 * modify the `response` object or create a new one. The function needs to return the `response`
12717 * object directly, or as a promise containing the `response` or a new `response` object.
12718 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
12719 * resolved with a rejection.
12720 *
12721 *
12722 * ```js
12723 * // register the interceptor as a service
12724 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
12725 * return {
12726 * // optional method
12727 * 'request': function(config) {
12728 * // do something on success
12729 * return config;
12730 * },
12731 *
12732 * // optional method
12733 * 'requestError': function(rejection) {
12734 * // do something on error
12735 * if (canRecover(rejection)) {
12736 * return responseOrNewPromise
12737 * }
12738 * return $q.reject(rejection);
12739 * },
12740 *
12741 *
12742 *
12743 * // optional method
12744 * 'response': function(response) {
12745 * // do something on success
12746 * return response;
12747 * },
12748 *
12749 * // optional method
12750 * 'responseError': function(rejection) {
12751 * // do something on error
12752 * if (canRecover(rejection)) {
12753 * return responseOrNewPromise
12754 * }
12755 * return $q.reject(rejection);
12756 * }
12757 * };
12758 * });
12759 *
12760 * $httpProvider.interceptors.push('myHttpInterceptor');
12761 *
12762 *
12763 * // alternatively, register the interceptor via an anonymous factory
12764 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
12765 * return {
12766 * 'request': function(config) {
12767 * // same as above
12768 * },
12769 *
12770 * 'response': function(response) {
12771 * // same as above
12772 * }
12773 * };
12774 * });
12775 * ```
12776 *
12777 * ## Security Considerations
12778 *
12779 * When designing web applications, consider security threats from:
12780 *
12781 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
12782 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
12783 *
12784 * Both server and the client must cooperate in order to eliminate these threats. AngularJS comes
12785 * pre-configured with strategies that address these issues, but for this to work backend server
12786 * cooperation is required.
12787 *
12788 * ### JSON Vulnerability Protection
12789 *
12790 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
12791 * allows third party website to turn your JSON resource URL into
12792 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
12793 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
12794 * AngularJS will automatically strip the prefix before processing it as JSON.
12795 *
12796 * For example if your server needs to return:
12797 * ```js
12798 * ['one','two']
12799 * ```
12800 *
12801 * which is vulnerable to attack, your server can return:
12802 * ```js
12803 * )]}',
12804 * ['one','two']
12805 * ```
12806 *
12807 * AngularJS will strip the prefix, before processing the JSON.
12808 *
12809 *
12810 * ### Cross Site Request Forgery (XSRF) Protection
12811 *
12812 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by
12813 * which the attacker can trick an authenticated user into unknowingly executing actions on your
12814 * website. AngularJS provides a mechanism to counter XSRF. When performing XHR requests, the
12815 * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP
12816 * header (by default `X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read
12817 * the cookie, your server can be assured that the XHR came from JavaScript running on your
12818 * domain.
12819 *
12820 * To take advantage of this, your server needs to set a token in a JavaScript readable session
12821 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
12822 * server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be
12823 * sure that only JavaScript running on your domain could have sent the request. The token must
12824 * be unique for each user and must be verifiable by the server (to prevent the JavaScript from
12825 * making up its own tokens). We recommend that the token is a digest of your site's
12826 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
12827 * for added security.
12828 *
12829 * The header will &mdash; by default &mdash; **not** be set for cross-domain requests. This
12830 * prevents unauthorized servers (e.g. malicious or compromised 3rd-party APIs) from gaining
12831 * access to your users' XSRF tokens and exposing them to Cross Site Request Forgery. If you
12832 * want to, you can trust additional origins to also receive the XSRF token, by adding them
12833 * to {@link ng.$httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins}. This might be
12834 * useful, for example, if your application, served from `example.com`, needs to access your API
12835 * at `api.example.com`.
12836 * See {@link ng.$httpProvider#xsrfTrustedOrigins $httpProvider.xsrfTrustedOrigins} for
12837 * more details.
12838 *
12839 * <div class="alert alert-danger">
12840 * **Warning**<br />
12841 * Only trusted origins that you have control over and make sure you understand the
12842 * implications of doing so.
12843 * </div>
12844 *
12845 * The name of the cookie and the header can be specified using the `xsrfCookieName` and
12846 * `xsrfHeaderName` properties of either `$httpProvider.defaults` at config-time,
12847 * `$http.defaults` at run-time, or the per-request config object.
12848 *
12849 * In order to prevent collisions in environments where multiple AngularJS apps share the
12850 * same domain or subdomain, we recommend that each application uses a unique cookie name.
12851 *
12852 *
12853 * @param {object} config Object describing the request to be made and how it should be
12854 * processed. The object has following properties:
12855 *
12856 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
12857 * - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested;
12858 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12859 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
12860 * with the `paramSerializer` and appended as GET parameters.
12861 * - **data** – `{string|Object}` – Data to be sent as the request message data.
12862 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
12863 * HTTP headers to send to the server. If the return value of a function is null, the
12864 * header will not be sent. Functions accept a config object as an argument.
12865 * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object.
12866 * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`.
12867 * The handler will be called in the context of a `$apply` block.
12868 * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload
12869 * object. To bind events to the XMLHttpRequest object, use `eventHandlers`.
12870 * The handler will be called in the context of a `$apply` block.
12871 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
12872 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
12873 * - **transformRequest** –
12874 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
12875 * transform function or an array of such functions. The transform function takes the http
12876 * request body and headers and returns its transformed (typically serialized) version.
12877 * See {@link ng.$http#overriding-the-default-transformations-per-request
12878 * Overriding the Default Transformations}
12879 * - **transformResponse** –
12880 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
12881 * transform function or an array of such functions. The transform function takes the http
12882 * response body, headers and status and returns its transformed (typically deserialized) version.
12883 * See {@link ng.$http#overriding-the-default-transformations-per-request
12884 * Overriding the Default Transformations}
12885 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
12886 * prepare the string representation of request parameters (specified as an object).
12887 * If specified as string, it is interpreted as function registered with the
12888 * {@link $injector $injector}, which means you can create your own serializer
12889 * by registering it as a {@link auto.$provide#service service}.
12890 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
12891 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
12892 * - **cache** – `{boolean|Object}` – A boolean value or object created with
12893 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response.
12894 * See {@link $http#caching $http Caching} for more information.
12895 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
12896 * that should abort the request when resolved.
12897 *
12898 * A numerical timeout or a promise returned from {@link ng.$timeout $timeout}, will set
12899 * the `xhrStatus` in the {@link $http#$http-returns response} to "timeout", and any other
12900 * resolved promise will set it to "abort", following standard XMLHttpRequest behavior.
12901 *
12902 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
12903 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
12904 * for more information.
12905 * - **responseType** - `{string}` - see
12906 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
12907 *
12908 * @returns {HttpPromise} A {@link ng.$q `Promise}` that will be resolved (request success)
12909 * or rejected (request failure) with a response object.
12910 *
12911 * The response object has these properties:
12912 *
12913 * - **data** – `{string|Object}` – The response body transformed with
12914 * the transform functions.
12915 * - **status** – `{number}` – HTTP status code of the response.
12916 * - **headers** – `{function([headerName])}` – Header getter function.
12917 * - **config** – `{Object}` – The configuration object that was used
12918 * to generate the request.
12919 * - **statusText** – `{string}` – HTTP status text of the response.
12920 * - **xhrStatus** – `{string}` – Status of the XMLHttpRequest
12921 * (`complete`, `error`, `timeout` or `abort`).
12922 *
12923 *
12924 * A response status code between 200 and 299 is considered a success status
12925 * and will result in the success callback being called. Any response status
12926 * code outside of that range is considered an error status and will result
12927 * in the error callback being called.
12928 * Also, status codes less than -1 are normalized to zero. -1 usually means
12929 * the request was aborted, e.g. using a `config.timeout`. More information
12930 * about the status might be available in the `xhrStatus` property.
12931 *
12932 * Note that if the response is a redirect, XMLHttpRequest will transparently
12933 * follow it, meaning that the outcome (success or error) will be determined
12934 * by the final response status code.
12935 *
12936 *
12937 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
12938 * requests. This is primarily meant to be used for debugging purposes.
12939 *
12940 *
12941 * @example
12942<example module="httpExample" name="http-service">
12943<file name="index.html">
12944 <div ng-controller="FetchController">
12945 <select ng-model="method" aria-label="Request method">
12946 <option>GET</option>
12947 <option>JSONP</option>
12948 </select>
12949 <input type="text" ng-model="url" size="80" aria-label="URL" />
12950 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
12951 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
12952 <button id="samplejsonpbtn"
12953 ng-click="updateModel('JSONP',
12954 'https://angularjs.org/greet.php?name=Super%20Hero')">
12955 Sample JSONP
12956 </button>
12957 <button id="invalidjsonpbtn"
12958 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist')">
12959 Invalid JSONP
12960 </button>
12961 <pre>http status code: {{status}}</pre>
12962 <pre>http response data: {{data}}</pre>
12963 </div>
12964</file>
12965<file name="script.js">
12966 angular.module('httpExample', [])
12967 .config(['$sceDelegateProvider', function($sceDelegateProvider) {
12968 // We must add the JSONP endpoint that we are using to the trusted list to show that we trust it
12969 $sceDelegateProvider.trustedResourceUrlList([
12970 'self',
12971 'https://angularjs.org/**'
12972 ]);
12973 }])
12974 .controller('FetchController', ['$scope', '$http', '$templateCache',
12975 function($scope, $http, $templateCache) {
12976 $scope.method = 'GET';
12977 $scope.url = 'http-hello.html';
12978
12979 $scope.fetch = function() {
12980 $scope.code = null;
12981 $scope.response = null;
12982
12983 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
12984 then(function(response) {
12985 $scope.status = response.status;
12986 $scope.data = response.data;
12987 }, function(response) {
12988 $scope.data = response.data || 'Request failed';
12989 $scope.status = response.status;
12990 });
12991 };
12992
12993 $scope.updateModel = function(method, url) {
12994 $scope.method = method;
12995 $scope.url = url;
12996 };
12997 }]);
12998</file>
12999<file name="http-hello.html">
13000 Hello, $http!
13001</file>
13002<file name="protractor.js" type="protractor">
13003 var status = element(by.binding('status'));
13004 var data = element(by.binding('data'));
13005 var fetchBtn = element(by.id('fetchbtn'));
13006 var sampleGetBtn = element(by.id('samplegetbtn'));
13007 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
13008
13009 it('should make an xhr GET request', function() {
13010 sampleGetBtn.click();
13011 fetchBtn.click();
13012 expect(status.getText()).toMatch('200');
13013 expect(data.getText()).toMatch(/Hello, \$http!/);
13014 });
13015
13016// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
13017// it('should make a JSONP request to angularjs.org', function() {
13018// var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
13019// sampleJsonpBtn.click();
13020// fetchBtn.click();
13021// expect(status.getText()).toMatch('200');
13022// expect(data.getText()).toMatch(/Super Hero!/);
13023// });
13024
13025 it('should make JSONP request to invalid URL and invoke the error handler',
13026 function() {
13027 invalidJsonpBtn.click();
13028 fetchBtn.click();
13029 expect(status.getText()).toMatch('0');
13030 expect(data.getText()).toMatch('Request failed');
13031 });
13032</file>
13033</example>
13034 */
13035 function $http(requestConfig) {
13036
13037 if (!isObject(requestConfig)) {
13038 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
13039 }
13040
13041 if (!isString($sce.valueOf(requestConfig.url))) {
13042 throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url);
13043 }
13044
13045 var config = extend({
13046 method: 'get',
13047 transformRequest: defaults.transformRequest,
13048 transformResponse: defaults.transformResponse,
13049 paramSerializer: defaults.paramSerializer,
13050 jsonpCallbackParam: defaults.jsonpCallbackParam
13051 }, requestConfig);
13052
13053 config.headers = mergeHeaders(requestConfig);
13054 config.method = uppercase(config.method);
13055 config.paramSerializer = isString(config.paramSerializer) ?
13056 $injector.get(config.paramSerializer) : config.paramSerializer;
13057
13058 $browser.$$incOutstandingRequestCount('$http');
13059
13060 var requestInterceptors = [];
13061 var responseInterceptors = [];
13062 var promise = $q.resolve(config);
13063
13064 // apply interceptors
13065 forEach(reversedInterceptors, function(interceptor) {
13066 if (interceptor.request || interceptor.requestError) {
13067 requestInterceptors.unshift(interceptor.request, interceptor.requestError);
13068 }
13069 if (interceptor.response || interceptor.responseError) {
13070 responseInterceptors.push(interceptor.response, interceptor.responseError);
13071 }
13072 });
13073
13074 promise = chainInterceptors(promise, requestInterceptors);
13075 promise = promise.then(serverRequest);
13076 promise = chainInterceptors(promise, responseInterceptors);
13077 promise = promise.finally(completeOutstandingRequest);
13078
13079 return promise;
13080
13081
13082 function chainInterceptors(promise, interceptors) {
13083 for (var i = 0, ii = interceptors.length; i < ii;) {
13084 var thenFn = interceptors[i++];
13085 var rejectFn = interceptors[i++];
13086
13087 promise = promise.then(thenFn, rejectFn);
13088 }
13089
13090 interceptors.length = 0;
13091
13092 return promise;
13093 }
13094
13095 function completeOutstandingRequest() {
13096 $browser.$$completeOutstandingRequest(noop, '$http');
13097 }
13098
13099 function executeHeaderFns(headers, config) {
13100 var headerContent, processedHeaders = {};
13101
13102 forEach(headers, function(headerFn, header) {
13103 if (isFunction(headerFn)) {
13104 headerContent = headerFn(config);
13105 if (headerContent != null) {
13106 processedHeaders[header] = headerContent;
13107 }
13108 } else {
13109 processedHeaders[header] = headerFn;
13110 }
13111 });
13112
13113 return processedHeaders;
13114 }
13115
13116 function mergeHeaders(config) {
13117 var defHeaders = defaults.headers,
13118 reqHeaders = extend({}, config.headers),
13119 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
13120
13121 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
13122
13123 // using for-in instead of forEach to avoid unnecessary iteration after header has been found
13124 defaultHeadersIteration:
13125 for (defHeaderName in defHeaders) {
13126 lowercaseDefHeaderName = lowercase(defHeaderName);
13127
13128 for (reqHeaderName in reqHeaders) {
13129 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
13130 continue defaultHeadersIteration;
13131 }
13132 }
13133
13134 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
13135 }
13136
13137 // execute if header value is a function for merged headers
13138 return executeHeaderFns(reqHeaders, shallowCopy(config));
13139 }
13140
13141 function serverRequest(config) {
13142 var headers = config.headers;
13143 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
13144
13145 // strip content-type if data is undefined
13146 if (isUndefined(reqData)) {
13147 forEach(headers, function(value, header) {
13148 if (lowercase(header) === 'content-type') {
13149 delete headers[header];
13150 }
13151 });
13152 }
13153
13154 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
13155 config.withCredentials = defaults.withCredentials;
13156 }
13157
13158 // send request
13159 return sendReq(config, reqData).then(transformResponse, transformResponse);
13160 }
13161
13162 function transformResponse(response) {
13163 // make a copy since the response must be cacheable
13164 var resp = extend({}, response);
13165 resp.data = transformData(response.data, response.headers, response.status,
13166 config.transformResponse);
13167 return (isSuccess(response.status))
13168 ? resp
13169 : $q.reject(resp);
13170 }
13171 }
13172
13173 $http.pendingRequests = [];
13174
13175 /**
13176 * @ngdoc method
13177 * @name $http#get
13178 *
13179 * @description
13180 * Shortcut method to perform `GET` request.
13181 *
13182 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
13183 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
13184 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
13185 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object.
13186 * See {@link ng.$http#$http-returns `$http()` return value}.
13187 */
13188
13189 /**
13190 * @ngdoc method
13191 * @name $http#delete
13192 *
13193 * @description
13194 * Shortcut method to perform `DELETE` request.
13195 *
13196 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
13197 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
13198 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
13199 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object.
13200 * See {@link ng.$http#$http-returns `$http()` return value}.
13201 */
13202
13203 /**
13204 * @ngdoc method
13205 * @name $http#head
13206 *
13207 * @description
13208 * Shortcut method to perform `HEAD` request.
13209 *
13210 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
13211 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
13212 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
13213 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object.
13214 * See {@link ng.$http#$http-returns `$http()` return value}.
13215 */
13216
13217 /**
13218 * @ngdoc method
13219 * @name $http#jsonp
13220 *
13221 * @description
13222 * Shortcut method to perform `JSONP` request.
13223 *
13224 * Note that, since JSONP requests are sensitive because the response is given full access to the browser,
13225 * the url must be declared, via {@link $sce} as a trusted resource URL.
13226 * You can trust a URL by adding it to the trusted resource URL list via
13227 * {@link $sceDelegateProvider#trustedResourceUrlList `$sceDelegateProvider.trustedResourceUrlList`} or
13228 * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}.
13229 *
13230 * You should avoid generating the URL for the JSONP request from user provided data.
13231 * Provide additional query parameters via `params` property of the `config` parameter, rather than
13232 * modifying the URL itself.
13233 *
13234 * JSONP requests must specify a callback to be used in the response from the server. This callback
13235 * is passed as a query parameter in the request. You must specify the name of this parameter by
13236 * setting the `jsonpCallbackParam` property on the request config object.
13237 *
13238 * ```
13239 * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'})
13240 * ```
13241 *
13242 * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`.
13243 * Initially this is set to `'callback'`.
13244 *
13245 * <div class="alert alert-danger">
13246 * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback
13247 * parameter value should go.
13248 * </div>
13249 *
13250 * If you would like to customise where and how the callbacks are stored then try overriding
13251 * or decorating the {@link $jsonpCallbacks} service.
13252 *
13253 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
13254 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
13255 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
13256 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object.
13257 * See {@link ng.$http#$http-returns `$http()` return value}.
13258 */
13259 createShortMethods('get', 'delete', 'head', 'jsonp');
13260
13261 /**
13262 * @ngdoc method
13263 * @name $http#post
13264 *
13265 * @description
13266 * Shortcut method to perform `POST` request.
13267 *
13268 * @param {string} url Relative or absolute URL specifying the destination of the request
13269 * @param {*} data Request content
13270 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
13271 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object.
13272 * See {@link ng.$http#$http-returns `$http()` return value}.
13273 */
13274
13275 /**
13276 * @ngdoc method
13277 * @name $http#put
13278 *
13279 * @description
13280 * Shortcut method to perform `PUT` request.
13281 *
13282 * @param {string} url Relative or absolute URL specifying the destination of the request
13283 * @param {*} data Request content
13284 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
13285 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object.
13286 * See {@link ng.$http#$http-returns `$http()` return value}.
13287 */
13288
13289 /**
13290 * @ngdoc method
13291 * @name $http#patch
13292 *
13293 * @description
13294 * Shortcut method to perform `PATCH` request.
13295 *
13296 * @param {string} url Relative or absolute URL specifying the destination of the request
13297 * @param {*} data Request content
13298 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
13299 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object.
13300 * See {@link ng.$http#$http-returns `$http()` return value}.
13301 */
13302 createShortMethodsWithData('post', 'put', 'patch');
13303
13304 /**
13305 * @ngdoc property
13306 * @name $http#defaults
13307 *
13308 * @description
13309 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
13310 * default headers, withCredentials as well as request and response transformations.
13311 *
13312 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
13313 */
13314 $http.defaults = defaults;
13315
13316
13317 return $http;
13318
13319
13320 function createShortMethods(names) {
13321 forEach(arguments, function(name) {
13322 $http[name] = function(url, config) {
13323 return $http(extend({}, config || {}, {
13324 method: name,
13325 url: url
13326 }));
13327 };
13328 });
13329 }
13330
13331
13332 function createShortMethodsWithData(name) {
13333 forEach(arguments, function(name) {
13334 $http[name] = function(url, data, config) {
13335 return $http(extend({}, config || {}, {
13336 method: name,
13337 url: url,
13338 data: data
13339 }));
13340 };
13341 });
13342 }
13343
13344
13345 /**
13346 * Makes the request.
13347 *
13348 * !!! ACCESSES CLOSURE VARS:
13349 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
13350 */
13351 function sendReq(config, reqData) {
13352 var deferred = $q.defer(),
13353 promise = deferred.promise,
13354 cache,
13355 cachedResp,
13356 reqHeaders = config.headers,
13357 isJsonp = lowercase(config.method) === 'jsonp',
13358 url = config.url;
13359
13360 if (isJsonp) {
13361 // JSONP is a pretty sensitive operation where we're allowing a script to have full access to
13362 // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL.
13363 url = $sce.getTrustedResourceUrl(url);
13364 } else if (!isString(url)) {
13365 // If it is not a string then the URL must be a $sce trusted object
13366 url = $sce.valueOf(url);
13367 }
13368
13369 url = buildUrl(url, config.paramSerializer(config.params));
13370
13371 if (isJsonp) {
13372 // Check the url and add the JSONP callback placeholder
13373 url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam);
13374 }
13375
13376 $http.pendingRequests.push(config);
13377 promise.then(removePendingReq, removePendingReq);
13378
13379 if ((config.cache || defaults.cache) && config.cache !== false &&
13380 (config.method === 'GET' || config.method === 'JSONP')) {
13381 cache = isObject(config.cache) ? config.cache
13382 : isObject(/** @type {?} */ (defaults).cache)
13383 ? /** @type {?} */ (defaults).cache
13384 : defaultCache;
13385 }
13386
13387 if (cache) {
13388 cachedResp = cache.get(url);
13389 if (isDefined(cachedResp)) {
13390 if (isPromiseLike(cachedResp)) {
13391 // cached request has already been sent, but there is no response yet
13392 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
13393 } else {
13394 // serving from cache
13395 if (isArray(cachedResp)) {
13396 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]);
13397 } else {
13398 resolvePromise(cachedResp, 200, {}, 'OK', 'complete');
13399 }
13400 }
13401 } else {
13402 // put the promise for the non-transformed response into cache as a placeholder
13403 cache.put(url, promise);
13404 }
13405 }
13406
13407
13408 // if we won't have the response in cache, set the xsrf headers and
13409 // send the request to the backend
13410 if (isUndefined(cachedResp)) {
13411 var xsrfValue = urlIsAllowedOrigin(config.url)
13412 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
13413 : undefined;
13414 if (xsrfValue) {
13415 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
13416 }
13417
13418 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
13419 config.withCredentials, config.responseType,
13420 createApplyHandlers(config.eventHandlers),
13421 createApplyHandlers(config.uploadEventHandlers));
13422 }
13423
13424 return promise;
13425
13426 function createApplyHandlers(eventHandlers) {
13427 if (eventHandlers) {
13428 var applyHandlers = {};
13429 forEach(eventHandlers, function(eventHandler, key) {
13430 applyHandlers[key] = function(event) {
13431 if (useApplyAsync) {
13432 $rootScope.$applyAsync(callEventHandler);
13433 } else if ($rootScope.$$phase) {
13434 callEventHandler();
13435 } else {
13436 $rootScope.$apply(callEventHandler);
13437 }
13438
13439 function callEventHandler() {
13440 eventHandler(event);
13441 }
13442 };
13443 });
13444 return applyHandlers;
13445 }
13446 }
13447
13448
13449 /**
13450 * Callback registered to $httpBackend():
13451 * - caches the response if desired
13452 * - resolves the raw $http promise
13453 * - calls $apply
13454 */
13455 function done(status, response, headersString, statusText, xhrStatus) {
13456 if (cache) {
13457 if (isSuccess(status)) {
13458 cache.put(url, [status, response, parseHeaders(headersString), statusText, xhrStatus]);
13459 } else {
13460 // remove promise from the cache
13461 cache.remove(url);
13462 }
13463 }
13464
13465 function resolveHttpPromise() {
13466 resolvePromise(response, status, headersString, statusText, xhrStatus);
13467 }
13468
13469 if (useApplyAsync) {
13470 $rootScope.$applyAsync(resolveHttpPromise);
13471 } else {
13472 resolveHttpPromise();
13473 if (!$rootScope.$$phase) $rootScope.$apply();
13474 }
13475 }
13476
13477
13478 /**
13479 * Resolves the raw $http promise.
13480 */
13481 function resolvePromise(response, status, headers, statusText, xhrStatus) {
13482 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
13483 status = status >= -1 ? status : 0;
13484
13485 (isSuccess(status) ? deferred.resolve : deferred.reject)({
13486 data: response,
13487 status: status,
13488 headers: headersGetter(headers),
13489 config: config,
13490 statusText: statusText,
13491 xhrStatus: xhrStatus
13492 });
13493 }
13494
13495 function resolvePromiseWithResult(result) {
13496 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText, result.xhrStatus);
13497 }
13498
13499 function removePendingReq() {
13500 var idx = $http.pendingRequests.indexOf(config);
13501 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
13502 }
13503 }
13504
13505
13506 function buildUrl(url, serializedParams) {
13507 if (serializedParams.length > 0) {
13508 url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams;
13509 }
13510 return url;
13511 }
13512
13513 function sanitizeJsonpCallbackParam(url, cbKey) {
13514 var parts = url.split('?');
13515 if (parts.length > 2) {
13516 // Throw if the url contains more than one `?` query indicator
13517 throw $httpMinErr('badjsonp', 'Illegal use more than one "?", in url, "{1}"', url);
13518 }
13519 var params = parseKeyValue(parts[1]);
13520 forEach(params, function(value, key) {
13521 if (value === 'JSON_CALLBACK') {
13522 // Throw if the url already contains a reference to JSON_CALLBACK
13523 throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url);
13524 }
13525 if (key === cbKey) {
13526 // Throw if the callback param was already provided
13527 throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', cbKey, url);
13528 }
13529 });
13530
13531 // Add in the JSON_CALLBACK callback param value
13532 url += ((url.indexOf('?') === -1) ? '?' : '&') + cbKey + '=JSON_CALLBACK';
13533
13534 return url;
13535 }
13536 }];
13537}
13538
13539/**
13540 * @ngdoc service
13541 * @name $xhrFactory
13542 * @this
13543 *
13544 * @description
13545 * Factory function used to create XMLHttpRequest objects.
13546 *
13547 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
13548 *
13549 * ```
13550 * angular.module('myApp', [])
13551 * .factory('$xhrFactory', function() {
13552 * return function createXhr(method, url) {
13553 * return new window.XMLHttpRequest({mozSystem: true});
13554 * };
13555 * });
13556 * ```
13557 *
13558 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
13559 * @param {string} url URL of the request.
13560 */
13561function $xhrFactoryProvider() {
13562 this.$get = function() {
13563 return function createXhr() {
13564 return new window.XMLHttpRequest();
13565 };
13566 };
13567}
13568
13569/**
13570 * @ngdoc service
13571 * @name $httpBackend
13572 * @requires $jsonpCallbacks
13573 * @requires $document
13574 * @requires $xhrFactory
13575 * @this
13576 *
13577 * @description
13578 * HTTP backend used by the {@link ng.$http service} that delegates to
13579 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
13580 *
13581 * You should never need to use this service directly, instead use the higher-level abstractions:
13582 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
13583 *
13584 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
13585 * $httpBackend} which can be trained with responses.
13586 */
13587function $HttpBackendProvider() {
13588 this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) {
13589 return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]);
13590 }];
13591}
13592
13593function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
13594 // TODO(vojta): fix the signature
13595 return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
13596 url = url || $browser.url();
13597
13598 if (lowercase(method) === 'jsonp') {
13599 var callbackPath = callbacks.createCallback(url);
13600 var jsonpDone = jsonpReq(url, callbackPath, function(status, text) {
13601 // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING)
13602 var response = (status === 200) && callbacks.getResponse(callbackPath);
13603 completeRequest(callback, status, response, '', text, 'complete');
13604 callbacks.removeCallback(callbackPath);
13605 });
13606 } else {
13607
13608 var xhr = createXhr(method, url);
13609 var abortedByTimeout = false;
13610
13611 xhr.open(method, url, true);
13612 forEach(headers, function(value, key) {
13613 if (isDefined(value)) {
13614 xhr.setRequestHeader(key, value);
13615 }
13616 });
13617
13618 xhr.onload = function requestLoaded() {
13619 var statusText = xhr.statusText || '';
13620
13621 // responseText is the old-school way of retrieving response (supported by IE9)
13622 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
13623 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
13624
13625 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
13626 var status = xhr.status === 1223 ? 204 : xhr.status;
13627
13628 // fix status code when it is 0 (0 status is undocumented).
13629 // Occurs when accessing file resources or on Android 4.1 stock browser
13630 // while retrieving files from application cache.
13631 if (status === 0) {
13632 status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0;
13633 }
13634
13635 completeRequest(callback,
13636 status,
13637 response,
13638 xhr.getAllResponseHeaders(),
13639 statusText,
13640 'complete');
13641 };
13642
13643 var requestError = function() {
13644 // The response is always empty
13645 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
13646 completeRequest(callback, -1, null, null, '', 'error');
13647 };
13648
13649 var requestAborted = function() {
13650 completeRequest(callback, -1, null, null, '', abortedByTimeout ? 'timeout' : 'abort');
13651 };
13652
13653 var requestTimeout = function() {
13654 // The response is always empty
13655 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
13656 completeRequest(callback, -1, null, null, '', 'timeout');
13657 };
13658
13659 xhr.onerror = requestError;
13660 xhr.ontimeout = requestTimeout;
13661 xhr.onabort = requestAborted;
13662
13663 forEach(eventHandlers, function(value, key) {
13664 xhr.addEventListener(key, value);
13665 });
13666
13667 forEach(uploadEventHandlers, function(value, key) {
13668 xhr.upload.addEventListener(key, value);
13669 });
13670
13671 if (withCredentials) {
13672 xhr.withCredentials = true;
13673 }
13674
13675 if (responseType) {
13676 try {
13677 xhr.responseType = responseType;
13678 } catch (e) {
13679 // WebKit added support for the json responseType value on 09/03/2013
13680 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
13681 // known to throw when setting the value "json" as the response type. Other older
13682 // browsers implementing the responseType
13683 //
13684 // The json response type can be ignored if not supported, because JSON payloads are
13685 // parsed on the client-side regardless.
13686 if (responseType !== 'json') {
13687 throw e;
13688 }
13689 }
13690 }
13691
13692 xhr.send(isUndefined(post) ? null : post);
13693 }
13694
13695 // Since we are using xhr.abort() when a request times out, we have to set a flag that
13696 // indicates to requestAborted if the request timed out or was aborted.
13697 //
13698 // http.timeout = numerical timeout timeout
13699 // http.timeout = $timeout timeout
13700 // http.timeout = promise abort
13701 // xhr.abort() abort (The xhr object is normally inaccessible, but
13702 // can be exposed with the xhrFactory)
13703 if (timeout > 0) {
13704 var timeoutId = $browserDefer(function() {
13705 timeoutRequest('timeout');
13706 }, timeout);
13707 } else if (isPromiseLike(timeout)) {
13708 timeout.then(function() {
13709 timeoutRequest(isDefined(timeout.$$timeoutId) ? 'timeout' : 'abort');
13710 });
13711 }
13712
13713 function timeoutRequest(reason) {
13714 abortedByTimeout = reason === 'timeout';
13715 if (jsonpDone) {
13716 jsonpDone();
13717 }
13718 if (xhr) {
13719 xhr.abort();
13720 }
13721 }
13722
13723 function completeRequest(callback, status, response, headersString, statusText, xhrStatus) {
13724 // cancel timeout and subsequent timeout promise resolution
13725 if (isDefined(timeoutId)) {
13726 $browserDefer.cancel(timeoutId);
13727 }
13728 jsonpDone = xhr = null;
13729
13730 callback(status, response, headersString, statusText, xhrStatus);
13731 }
13732 };
13733
13734 function jsonpReq(url, callbackPath, done) {
13735 url = url.replace('JSON_CALLBACK', callbackPath);
13736 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
13737 // - fetches local scripts via XHR and evals them
13738 // - adds and immediately removes script elements from the document
13739 var script = rawDocument.createElement('script'), callback = null;
13740 script.type = 'text/javascript';
13741 script.src = url;
13742 script.async = true;
13743
13744 callback = function(event) {
13745 script.removeEventListener('load', callback);
13746 script.removeEventListener('error', callback);
13747 rawDocument.body.removeChild(script);
13748 script = null;
13749 var status = -1;
13750 var text = 'unknown';
13751
13752 if (event) {
13753 if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) {
13754 event = { type: 'error' };
13755 }
13756 text = event.type;
13757 status = event.type === 'error' ? 404 : 200;
13758 }
13759
13760 if (done) {
13761 done(status, text);
13762 }
13763 };
13764
13765 script.addEventListener('load', callback);
13766 script.addEventListener('error', callback);
13767 rawDocument.body.appendChild(script);
13768 return callback;
13769 }
13770}
13771
13772var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
13773$interpolateMinErr.throwNoconcat = function(text) {
13774 throw $interpolateMinErr('noconcat',
13775 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' +
13776 'interpolations that concatenate multiple expressions when a trusted value is ' +
13777 'required. See http://docs.angularjs.org/api/ng.$sce', text);
13778};
13779
13780$interpolateMinErr.interr = function(text, err) {
13781 return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString());
13782};
13783
13784/**
13785 * @ngdoc provider
13786 * @name $interpolateProvider
13787 * @this
13788 *
13789 * @description
13790 *
13791 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
13792 *
13793 * <div class="alert alert-danger">
13794 * This feature is sometimes used to mix different markup languages, e.g. to wrap an AngularJS
13795 * template within a Python Jinja template (or any other template language). Mixing templating
13796 * languages is **very dangerous**. The embedding template language will not safely escape AngularJS
13797 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
13798 * security bugs!
13799 * </div>
13800 *
13801 * @example
13802<example name="custom-interpolation-markup" module="customInterpolationApp">
13803<file name="index.html">
13804<script>
13805 var customInterpolationApp = angular.module('customInterpolationApp', []);
13806
13807 customInterpolationApp.config(function($interpolateProvider) {
13808 $interpolateProvider.startSymbol('//');
13809 $interpolateProvider.endSymbol('//');
13810 });
13811
13812
13813 customInterpolationApp.controller('DemoController', function() {
13814 this.label = "This binding is brought you by // interpolation symbols.";
13815 });
13816</script>
13817<div ng-controller="DemoController as demo">
13818 //demo.label//
13819</div>
13820</file>
13821<file name="protractor.js" type="protractor">
13822 it('should interpolate binding with custom symbols', function() {
13823 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
13824 });
13825</file>
13826</example>
13827 */
13828function $InterpolateProvider() {
13829 var startSymbol = '{{';
13830 var endSymbol = '}}';
13831
13832 /**
13833 * @ngdoc method
13834 * @name $interpolateProvider#startSymbol
13835 * @description
13836 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
13837 *
13838 * @param {string=} value new value to set the starting symbol to.
13839 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
13840 */
13841 this.startSymbol = function(value) {
13842 if (value) {
13843 startSymbol = value;
13844 return this;
13845 }
13846 return startSymbol;
13847 };
13848
13849 /**
13850 * @ngdoc method
13851 * @name $interpolateProvider#endSymbol
13852 * @description
13853 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
13854 *
13855 * @param {string=} value new value to set the ending symbol to.
13856 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
13857 */
13858 this.endSymbol = function(value) {
13859 if (value) {
13860 endSymbol = value;
13861 return this;
13862 }
13863 return endSymbol;
13864 };
13865
13866
13867 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
13868 var startSymbolLength = startSymbol.length,
13869 endSymbolLength = endSymbol.length,
13870 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
13871 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
13872
13873 function escape(ch) {
13874 return '\\\\\\' + ch;
13875 }
13876
13877 function unescapeText(text) {
13878 return text.replace(escapedStartRegexp, startSymbol).
13879 replace(escapedEndRegexp, endSymbol);
13880 }
13881
13882 // TODO: this is the same as the constantWatchDelegate in parse.js
13883 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
13884 var unwatch = scope.$watch(function constantInterpolateWatch(scope) {
13885 unwatch();
13886 return constantInterp(scope);
13887 }, listener, objectEquality);
13888 return unwatch;
13889 }
13890
13891 /**
13892 * @ngdoc service
13893 * @name $interpolate
13894 * @kind function
13895 *
13896 * @requires $parse
13897 * @requires $sce
13898 *
13899 * @description
13900 *
13901 * Compiles a string with markup into an interpolation function. This service is used by the
13902 * HTML {@link ng.$compile $compile} service for data binding. See
13903 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
13904 * interpolation markup.
13905 *
13906 *
13907 * ```js
13908 * var $interpolate = ...; // injected
13909 * var exp = $interpolate('Hello {{name | uppercase}}!');
13910 * expect(exp({name:'AngularJS'})).toEqual('Hello ANGULARJS!');
13911 * ```
13912 *
13913 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
13914 * `true`, the interpolation function will return `undefined` unless all embedded expressions
13915 * evaluate to a value other than `undefined`.
13916 *
13917 * ```js
13918 * var $interpolate = ...; // injected
13919 * var context = {greeting: 'Hello', name: undefined };
13920 *
13921 * // default "forgiving" mode
13922 * var exp = $interpolate('{{greeting}} {{name}}!');
13923 * expect(exp(context)).toEqual('Hello !');
13924 *
13925 * // "allOrNothing" mode
13926 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
13927 * expect(exp(context)).toBeUndefined();
13928 * context.name = 'AngularJS';
13929 * expect(exp(context)).toEqual('Hello AngularJS!');
13930 * ```
13931 *
13932 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
13933 *
13934 * #### Escaped Interpolation
13935 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
13936 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
13937 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
13938 * or binding.
13939 *
13940 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
13941 * degree, while also enabling code examples to work without relying on the
13942 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
13943 *
13944 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
13945 * replacing angle brackets (&lt;, &gt;) with &amp;lt; and &amp;gt; respectively, and replacing all
13946 * interpolation start/end markers with their escaped counterparts.**
13947 *
13948 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
13949 * output when the $interpolate service processes the text. So, for HTML elements interpolated
13950 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
13951 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
13952 * this is typically useful only when user-data is used in rendering a template from the server, or
13953 * when otherwise untrusted data is used by a directive.
13954 *
13955 * <example name="interpolation">
13956 * <file name="index.html">
13957 * <div ng-init="username='A user'">
13958 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
13959 * </p>
13960 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
13961 * application, but fails to accomplish their task, because the server has correctly
13962 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
13963 * characters.</p>
13964 * <p>Instead, the result of the attempted script injection is visible, and can be removed
13965 * from the database by an administrator.</p>
13966 * </div>
13967 * </file>
13968 * </example>
13969 *
13970 * @knownIssue
13971 * It is currently not possible for an interpolated expression to contain the interpolation end
13972 * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
13973 * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
13974 *
13975 * @knownIssue
13976 * All directives and components must use the standard `{{` `}}` interpolation symbols
13977 * in their templates. If you change the application interpolation symbols the {@link $compile}
13978 * service will attempt to denormalize the standard symbols to the custom symbols.
13979 * The denormalization process is not clever enough to know not to replace instances of the standard
13980 * symbols where they would not normally be treated as interpolation symbols. For example in the following
13981 * code snippet the closing braces of the literal object will get incorrectly denormalized:
13982 *
13983 * ```
13984 * <div data-context='{"context":{"id":3,"type":"page"}}">
13985 * ```
13986 *
13987 * The workaround is to ensure that such instances are separated by whitespace:
13988 * ```
13989 * <div data-context='{"context":{"id":3,"type":"page"} }">
13990 * ```
13991 *
13992 * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information.
13993 *
13994 * @param {string} text The text with markup to interpolate.
13995 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
13996 * embedded expression in order to return an interpolation function. Strings with no
13997 * embedded expression will return null for the interpolation function.
13998 * @param {string=} trustedContext when provided, the returned function passes the interpolated
13999 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
14000 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
14001 * provides Strict Contextual Escaping for details.
14002 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
14003 * unless all embedded expressions evaluate to a value other than `undefined`.
14004 * @returns {function(context)} an interpolation function which is used to compute the
14005 * interpolated string. The function has these parameters:
14006 *
14007 * - `context`: evaluation context for all expressions embedded in the interpolated text
14008 */
14009 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
14010 var contextAllowsConcatenation = trustedContext === $sce.URL || trustedContext === $sce.MEDIA_URL;
14011
14012 // Provide a quick exit and simplified result function for text with no interpolation
14013 if (!text.length || text.indexOf(startSymbol) === -1) {
14014 if (mustHaveExpression) return;
14015
14016 var unescapedText = unescapeText(text);
14017 if (contextAllowsConcatenation) {
14018 unescapedText = $sce.getTrusted(trustedContext, unescapedText);
14019 }
14020 var constantInterp = valueFn(unescapedText);
14021 constantInterp.exp = text;
14022 constantInterp.expressions = [];
14023 constantInterp.$$watchDelegate = constantWatchDelegate;
14024
14025 return constantInterp;
14026 }
14027
14028 allOrNothing = !!allOrNothing;
14029 var startIndex,
14030 endIndex,
14031 index = 0,
14032 expressions = [],
14033 parseFns,
14034 textLength = text.length,
14035 exp,
14036 concat = [],
14037 expressionPositions = [],
14038 singleExpression;
14039
14040
14041 while (index < textLength) {
14042 if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
14043 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
14044 if (index !== startIndex) {
14045 concat.push(unescapeText(text.substring(index, startIndex)));
14046 }
14047 exp = text.substring(startIndex + startSymbolLength, endIndex);
14048 expressions.push(exp);
14049 index = endIndex + endSymbolLength;
14050 expressionPositions.push(concat.length);
14051 concat.push(''); // Placeholder that will get replaced with the evaluated expression.
14052 } else {
14053 // we did not find an interpolation, so we have to add the remainder to the separators array
14054 if (index !== textLength) {
14055 concat.push(unescapeText(text.substring(index)));
14056 }
14057 break;
14058 }
14059 }
14060
14061 singleExpression = concat.length === 1 && expressionPositions.length === 1;
14062 // Intercept expression if we need to stringify concatenated inputs, which may be SCE trusted
14063 // objects rather than simple strings
14064 // (we don't modify the expression if the input consists of only a single trusted input)
14065 var interceptor = contextAllowsConcatenation && singleExpression ? undefined : parseStringifyInterceptor;
14066 parseFns = expressions.map(function(exp) { return $parse(exp, interceptor); });
14067
14068 // Concatenating expressions makes it hard to reason about whether some combination of
14069 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
14070 // single expression be used for some $sce-managed secure contexts (RESOURCE_URLs mostly),
14071 // we ensure that the value that's used is assigned or constructed by some JS code somewhere
14072 // that is more testable or make it obvious that you bound the value to some user controlled
14073 // value. This helps reduce the load when auditing for XSS issues.
14074
14075 // Note that URL and MEDIA_URL $sce contexts do not need this, since `$sce` can sanitize the values
14076 // passed to it. In that case, `$sce.getTrusted` will be called on either the single expression
14077 // or on the overall concatenated string (losing trusted types used in the mix, by design).
14078 // Both these methods will sanitize plain strings. Also, HTML could be included, but since it's
14079 // only used in srcdoc attributes, this would not be very useful.
14080
14081 if (!mustHaveExpression || expressions.length) {
14082 var compute = function(values) {
14083 for (var i = 0, ii = expressions.length; i < ii; i++) {
14084 if (allOrNothing && isUndefined(values[i])) return;
14085 concat[expressionPositions[i]] = values[i];
14086 }
14087
14088 if (contextAllowsConcatenation) {
14089 // If `singleExpression` then `concat[0]` might be a "trusted" value or `null`, rather than a string
14090 return $sce.getTrusted(trustedContext, singleExpression ? concat[0] : concat.join(''));
14091 } else if (trustedContext && concat.length > 1) {
14092 // This context does not allow more than one part, e.g. expr + string or exp + exp.
14093 $interpolateMinErr.throwNoconcat(text);
14094 }
14095 // In an unprivileged context or only one part: just concatenate and return.
14096 return concat.join('');
14097 };
14098
14099 return extend(function interpolationFn(context) {
14100 var i = 0;
14101 var ii = expressions.length;
14102 var values = new Array(ii);
14103
14104 try {
14105 for (; i < ii; i++) {
14106 values[i] = parseFns[i](context);
14107 }
14108
14109 return compute(values);
14110 } catch (err) {
14111 $exceptionHandler($interpolateMinErr.interr(text, err));
14112 }
14113
14114 }, {
14115 // all of these properties are undocumented for now
14116 exp: text, //just for compatibility with regular watchers created via $watch
14117 expressions: expressions,
14118 $$watchDelegate: function(scope, listener) {
14119 var lastValue;
14120 return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) {
14121 var currValue = compute(values);
14122 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
14123 lastValue = currValue;
14124 });
14125 }
14126 });
14127 }
14128
14129 function parseStringifyInterceptor(value) {
14130 try {
14131 // In concatenable contexts, getTrusted comes at the end, to avoid sanitizing individual
14132 // parts of a full URL. We don't care about losing the trustedness here.
14133 // In non-concatenable contexts, where there is only one expression, this interceptor is
14134 // not applied to the expression.
14135 value = (trustedContext && !contextAllowsConcatenation) ?
14136 $sce.getTrusted(trustedContext, value) :
14137 $sce.valueOf(value);
14138 return allOrNothing && !isDefined(value) ? value : stringify(value);
14139 } catch (err) {
14140 $exceptionHandler($interpolateMinErr.interr(text, err));
14141 }
14142 }
14143 }
14144
14145
14146 /**
14147 * @ngdoc method
14148 * @name $interpolate#startSymbol
14149 * @description
14150 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
14151 *
14152 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
14153 * the symbol.
14154 *
14155 * @returns {string} start symbol.
14156 */
14157 $interpolate.startSymbol = function() {
14158 return startSymbol;
14159 };
14160
14161
14162 /**
14163 * @ngdoc method
14164 * @name $interpolate#endSymbol
14165 * @description
14166 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
14167 *
14168 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
14169 * the symbol.
14170 *
14171 * @returns {string} end symbol.
14172 */
14173 $interpolate.endSymbol = function() {
14174 return endSymbol;
14175 };
14176
14177 return $interpolate;
14178 }];
14179}
14180
14181var $intervalMinErr = minErr('$interval');
14182
14183/** @this */
14184function $IntervalProvider() {
14185 this.$get = ['$$intervalFactory', '$window',
14186 function($$intervalFactory, $window) {
14187 var intervals = {};
14188 var setIntervalFn = function(tick, delay, deferred) {
14189 var id = $window.setInterval(tick, delay);
14190 intervals[id] = deferred;
14191 return id;
14192 };
14193 var clearIntervalFn = function(id) {
14194 $window.clearInterval(id);
14195 delete intervals[id];
14196 };
14197
14198 /**
14199 * @ngdoc service
14200 * @name $interval
14201 *
14202 * @description
14203 * AngularJS's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
14204 * milliseconds.
14205 *
14206 * The return value of registering an interval function is a promise. This promise will be
14207 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
14208 * run indefinitely if `count` is not defined. The value of the notification will be the
14209 * number of iterations that have run.
14210 * To cancel an interval, call `$interval.cancel(promise)`.
14211 *
14212 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
14213 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
14214 * time.
14215 *
14216 * <div class="alert alert-warning">
14217 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
14218 * with them. In particular they are not automatically destroyed when a controller's scope or a
14219 * directive's element are destroyed.
14220 * You should take this into consideration and make sure to always cancel the interval at the
14221 * appropriate moment. See the example below for more details on how and when to do this.
14222 * </div>
14223 *
14224 * @param {function()} fn A function that should be called repeatedly. If no additional arguments
14225 * are passed (see below), the function is called with the current iteration count.
14226 * @param {number} delay Number of milliseconds between each function call.
14227 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
14228 * indefinitely.
14229 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
14230 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
14231 * @param {...*=} Pass additional parameters to the executed function.
14232 * @returns {promise} A promise which will be notified on each iteration. It will resolve once all iterations of the interval complete.
14233 *
14234 * @example
14235 * <example module="intervalExample" name="interval-service">
14236 * <file name="index.html">
14237 * <script>
14238 * angular.module('intervalExample', [])
14239 * .controller('ExampleController', ['$scope', '$interval',
14240 * function($scope, $interval) {
14241 * $scope.format = 'M/d/yy h:mm:ss a';
14242 * $scope.blood_1 = 100;
14243 * $scope.blood_2 = 120;
14244 *
14245 * var stop;
14246 * $scope.fight = function() {
14247 * // Don't start a new fight if we are already fighting
14248 * if ( angular.isDefined(stop) ) return;
14249 *
14250 * stop = $interval(function() {
14251 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
14252 * $scope.blood_1 = $scope.blood_1 - 3;
14253 * $scope.blood_2 = $scope.blood_2 - 4;
14254 * } else {
14255 * $scope.stopFight();
14256 * }
14257 * }, 100);
14258 * };
14259 *
14260 * $scope.stopFight = function() {
14261 * if (angular.isDefined(stop)) {
14262 * $interval.cancel(stop);
14263 * stop = undefined;
14264 * }
14265 * };
14266 *
14267 * $scope.resetFight = function() {
14268 * $scope.blood_1 = 100;
14269 * $scope.blood_2 = 120;
14270 * };
14271 *
14272 * $scope.$on('$destroy', function() {
14273 * // Make sure that the interval is destroyed too
14274 * $scope.stopFight();
14275 * });
14276 * }])
14277 * // Register the 'myCurrentTime' directive factory method.
14278 * // We inject $interval and dateFilter service since the factory method is DI.
14279 * .directive('myCurrentTime', ['$interval', 'dateFilter',
14280 * function($interval, dateFilter) {
14281 * // return the directive link function. (compile function not needed)
14282 * return function(scope, element, attrs) {
14283 * var format, // date format
14284 * stopTime; // so that we can cancel the time updates
14285 *
14286 * // used to update the UI
14287 * function updateTime() {
14288 * element.text(dateFilter(new Date(), format));
14289 * }
14290 *
14291 * // watch the expression, and update the UI on change.
14292 * scope.$watch(attrs.myCurrentTime, function(value) {
14293 * format = value;
14294 * updateTime();
14295 * });
14296 *
14297 * stopTime = $interval(updateTime, 1000);
14298 *
14299 * // listen on DOM destroy (removal) event, and cancel the next UI update
14300 * // to prevent updating time after the DOM element was removed.
14301 * element.on('$destroy', function() {
14302 * $interval.cancel(stopTime);
14303 * });
14304 * }
14305 * }]);
14306 * </script>
14307 *
14308 * <div>
14309 * <div ng-controller="ExampleController">
14310 * <label>Date format: <input ng-model="format"></label> <hr/>
14311 * Current time is: <span my-current-time="format"></span>
14312 * <hr/>
14313 * Blood 1 : <font color='red'>{{blood_1}}</font>
14314 * Blood 2 : <font color='red'>{{blood_2}}</font>
14315 * <button type="button" data-ng-click="fight()">Fight</button>
14316 * <button type="button" data-ng-click="stopFight()">StopFight</button>
14317 * <button type="button" data-ng-click="resetFight()">resetFight</button>
14318 * </div>
14319 * </div>
14320 *
14321 * </file>
14322 * </example>
14323 */
14324 var interval = $$intervalFactory(setIntervalFn, clearIntervalFn);
14325
14326 /**
14327 * @ngdoc method
14328 * @name $interval#cancel
14329 *
14330 * @description
14331 * Cancels a task associated with the `promise`.
14332 *
14333 * @param {Promise=} promise returned by the `$interval` function.
14334 * @returns {boolean} Returns `true` if the task was successfully canceled.
14335 */
14336 interval.cancel = function(promise) {
14337 if (!promise) return false;
14338
14339 if (!promise.hasOwnProperty('$$intervalId')) {
14340 throw $intervalMinErr('badprom',
14341 '`$interval.cancel()` called with a promise that was not generated by `$interval()`.');
14342 }
14343
14344 if (!intervals.hasOwnProperty(promise.$$intervalId)) return false;
14345
14346 var id = promise.$$intervalId;
14347 var deferred = intervals[id];
14348
14349 // Interval cancels should not report an unhandled promise.
14350 markQExceptionHandled(deferred.promise);
14351 deferred.reject('canceled');
14352 clearIntervalFn(id);
14353
14354 return true;
14355 };
14356
14357 return interval;
14358 }];
14359}
14360
14361/** @this */
14362function $$IntervalFactoryProvider() {
14363 this.$get = ['$browser', '$q', '$$q', '$rootScope',
14364 function($browser, $q, $$q, $rootScope) {
14365 return function intervalFactory(setIntervalFn, clearIntervalFn) {
14366 return function intervalFn(fn, delay, count, invokeApply) {
14367 var hasParams = arguments.length > 4,
14368 args = hasParams ? sliceArgs(arguments, 4) : [],
14369 iteration = 0,
14370 skipApply = isDefined(invokeApply) && !invokeApply,
14371 deferred = (skipApply ? $$q : $q).defer(),
14372 promise = deferred.promise;
14373
14374 count = isDefined(count) ? count : 0;
14375
14376 function callback() {
14377 if (!hasParams) {
14378 fn(iteration);
14379 } else {
14380 fn.apply(null, args);
14381 }
14382 }
14383
14384 function tick() {
14385 if (skipApply) {
14386 $browser.defer(callback);
14387 } else {
14388 $rootScope.$evalAsync(callback);
14389 }
14390 deferred.notify(iteration++);
14391
14392 if (count > 0 && iteration >= count) {
14393 deferred.resolve(iteration);
14394 clearIntervalFn(promise.$$intervalId);
14395 }
14396
14397 if (!skipApply) $rootScope.$apply();
14398 }
14399
14400 promise.$$intervalId = setIntervalFn(tick, delay, deferred, skipApply);
14401
14402 return promise;
14403 };
14404 };
14405 }];
14406}
14407
14408/**
14409 * @ngdoc service
14410 * @name $jsonpCallbacks
14411 * @requires $window
14412 * @description
14413 * This service handles the lifecycle of callbacks to handle JSONP requests.
14414 * Override this service if you wish to customise where the callbacks are stored and
14415 * how they vary compared to the requested url.
14416 */
14417var $jsonpCallbacksProvider = /** @this */ function() {
14418 this.$get = function() {
14419 var callbacks = angular.callbacks;
14420 var callbackMap = {};
14421
14422 function createCallback(callbackId) {
14423 var callback = function(data) {
14424 callback.data = data;
14425 callback.called = true;
14426 };
14427 callback.id = callbackId;
14428 return callback;
14429 }
14430
14431 return {
14432 /**
14433 * @ngdoc method
14434 * @name $jsonpCallbacks#createCallback
14435 * @param {string} url the url of the JSONP request
14436 * @returns {string} the callback path to send to the server as part of the JSONP request
14437 * @description
14438 * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback
14439 * to pass to the server, which will be used to call the callback with its payload in the JSONP response.
14440 */
14441 createCallback: function(url) {
14442 var callbackId = '_' + (callbacks.$$counter++).toString(36);
14443 var callbackPath = 'angular.callbacks.' + callbackId;
14444 var callback = createCallback(callbackId);
14445 callbackMap[callbackPath] = callbacks[callbackId] = callback;
14446 return callbackPath;
14447 },
14448 /**
14449 * @ngdoc method
14450 * @name $jsonpCallbacks#wasCalled
14451 * @param {string} callbackPath the path to the callback that was sent in the JSONP request
14452 * @returns {boolean} whether the callback has been called, as a result of the JSONP response
14453 * @description
14454 * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the
14455 * callback that was passed in the request.
14456 */
14457 wasCalled: function(callbackPath) {
14458 return callbackMap[callbackPath].called;
14459 },
14460 /**
14461 * @ngdoc method
14462 * @name $jsonpCallbacks#getResponse
14463 * @param {string} callbackPath the path to the callback that was sent in the JSONP request
14464 * @returns {*} the data received from the response via the registered callback
14465 * @description
14466 * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback
14467 * in the JSONP response.
14468 */
14469 getResponse: function(callbackPath) {
14470 return callbackMap[callbackPath].data;
14471 },
14472 /**
14473 * @ngdoc method
14474 * @name $jsonpCallbacks#removeCallback
14475 * @param {string} callbackPath the path to the callback that was sent in the JSONP request
14476 * @description
14477 * {@link $httpBackend} calls this method to remove the callback after the JSONP request has
14478 * completed or timed-out.
14479 */
14480 removeCallback: function(callbackPath) {
14481 var callback = callbackMap[callbackPath];
14482 delete callbacks[callback.id];
14483 delete callbackMap[callbackPath];
14484 }
14485 };
14486 };
14487};
14488
14489/**
14490 * @ngdoc service
14491 * @name $locale
14492 *
14493 * @description
14494 * $locale service provides localization rules for various AngularJS components. As of right now the
14495 * only public api is:
14496 *
14497 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
14498 */
14499
14500/* global stripHash: true */
14501
14502var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/,
14503 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
14504var $locationMinErr = minErr('$location');
14505
14506
14507/**
14508 * Encode path using encodeUriSegment, ignoring forward slashes
14509 *
14510 * @param {string} path Path to encode
14511 * @returns {string}
14512 */
14513function encodePath(path) {
14514 var segments = path.split('/'),
14515 i = segments.length;
14516
14517 while (i--) {
14518 // decode forward slashes to prevent them from being double encoded
14519 segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, '/'));
14520 }
14521
14522 return segments.join('/');
14523}
14524
14525function decodePath(path, html5Mode) {
14526 var segments = path.split('/'),
14527 i = segments.length;
14528
14529 while (i--) {
14530 segments[i] = decodeURIComponent(segments[i]);
14531 if (html5Mode) {
14532 // encode forward slashes to prevent them from being mistaken for path separators
14533 segments[i] = segments[i].replace(/\//g, '%2F');
14534 }
14535 }
14536
14537 return segments.join('/');
14538}
14539
14540function normalizePath(pathValue, searchValue, hashValue) {
14541 var search = toKeyValue(searchValue),
14542 hash = hashValue ? '#' + encodeUriSegment(hashValue) : '',
14543 path = encodePath(pathValue);
14544
14545 return path + (search ? '?' + search : '') + hash;
14546}
14547
14548function parseAbsoluteUrl(absoluteUrl, locationObj) {
14549 var parsedUrl = urlResolve(absoluteUrl);
14550
14551 locationObj.$$protocol = parsedUrl.protocol;
14552 locationObj.$$host = parsedUrl.hostname;
14553 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
14554}
14555
14556var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/;
14557function parseAppUrl(url, locationObj, html5Mode) {
14558
14559 if (DOUBLE_SLASH_REGEX.test(url)) {
14560 throw $locationMinErr('badpath', 'Invalid url "{0}".', url);
14561 }
14562
14563 var prefixed = (url.charAt(0) !== '/');
14564 if (prefixed) {
14565 url = '/' + url;
14566 }
14567 var match = urlResolve(url);
14568 var path = prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname;
14569 locationObj.$$path = decodePath(path, html5Mode);
14570 locationObj.$$search = parseKeyValue(match.search);
14571 locationObj.$$hash = decodeURIComponent(match.hash);
14572
14573 // make sure path starts with '/';
14574 if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') {
14575 locationObj.$$path = '/' + locationObj.$$path;
14576 }
14577}
14578
14579function startsWith(str, search) {
14580 return str.slice(0, search.length) === search;
14581}
14582
14583/**
14584 *
14585 * @param {string} base
14586 * @param {string} url
14587 * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
14588 * the expected string.
14589 */
14590function stripBaseUrl(base, url) {
14591 if (startsWith(url, base)) {
14592 return url.substr(base.length);
14593 }
14594}
14595
14596function stripHash(url) {
14597 var index = url.indexOf('#');
14598 return index === -1 ? url : url.substr(0, index);
14599}
14600
14601function stripFile(url) {
14602 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
14603}
14604
14605/* return the server only (scheme://host:port) */
14606function serverBase(url) {
14607 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
14608}
14609
14610
14611/**
14612 * LocationHtml5Url represents a URL
14613 * This object is exposed as $location service when HTML5 mode is enabled and supported
14614 *
14615 * @constructor
14616 * @param {string} appBase application base URL
14617 * @param {string} appBaseNoFile application base URL stripped of any filename
14618 * @param {string} basePrefix URL path prefix
14619 */
14620function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
14621 this.$$html5 = true;
14622 basePrefix = basePrefix || '';
14623 parseAbsoluteUrl(appBase, this);
14624
14625
14626 /**
14627 * Parse given HTML5 (regular) URL string into properties
14628 * @param {string} url HTML5 URL
14629 * @private
14630 */
14631 this.$$parse = function(url) {
14632 var pathUrl = stripBaseUrl(appBaseNoFile, url);
14633 if (!isString(pathUrl)) {
14634 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
14635 appBaseNoFile);
14636 }
14637
14638 parseAppUrl(pathUrl, this, true);
14639
14640 if (!this.$$path) {
14641 this.$$path = '/';
14642 }
14643
14644 this.$$compose();
14645 };
14646
14647 this.$$normalizeUrl = function(url) {
14648 return appBaseNoFile + url.substr(1); // first char is always '/'
14649 };
14650
14651 this.$$parseLinkUrl = function(url, relHref) {
14652 if (relHref && relHref[0] === '#') {
14653 // special case for links to hash fragments:
14654 // keep the old url and only replace the hash fragment
14655 this.hash(relHref.slice(1));
14656 return true;
14657 }
14658 var appUrl, prevAppUrl;
14659 var rewrittenUrl;
14660
14661
14662 if (isDefined(appUrl = stripBaseUrl(appBase, url))) {
14663 prevAppUrl = appUrl;
14664 if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) {
14665 rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl);
14666 } else {
14667 rewrittenUrl = appBase + prevAppUrl;
14668 }
14669 } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) {
14670 rewrittenUrl = appBaseNoFile + appUrl;
14671 } else if (appBaseNoFile === url + '/') {
14672 rewrittenUrl = appBaseNoFile;
14673 }
14674 if (rewrittenUrl) {
14675 this.$$parse(rewrittenUrl);
14676 }
14677 return !!rewrittenUrl;
14678 };
14679}
14680
14681
14682/**
14683 * LocationHashbangUrl represents URL
14684 * This object is exposed as $location service when developer doesn't opt into html5 mode.
14685 * It also serves as the base class for html5 mode fallback on legacy browsers.
14686 *
14687 * @constructor
14688 * @param {string} appBase application base URL
14689 * @param {string} appBaseNoFile application base URL stripped of any filename
14690 * @param {string} hashPrefix hashbang prefix
14691 */
14692function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
14693
14694 parseAbsoluteUrl(appBase, this);
14695
14696
14697 /**
14698 * Parse given hashbang URL into properties
14699 * @param {string} url Hashbang URL
14700 * @private
14701 */
14702 this.$$parse = function(url) {
14703 var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url);
14704 var withoutHashUrl;
14705
14706 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
14707
14708 // The rest of the URL starts with a hash so we have
14709 // got either a hashbang path or a plain hash fragment
14710 withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl);
14711 if (isUndefined(withoutHashUrl)) {
14712 // There was no hashbang prefix so we just have a hash fragment
14713 withoutHashUrl = withoutBaseUrl;
14714 }
14715
14716 } else {
14717 // There was no hashbang path nor hash fragment:
14718 // If we are in HTML5 mode we use what is left as the path;
14719 // Otherwise we ignore what is left
14720 if (this.$$html5) {
14721 withoutHashUrl = withoutBaseUrl;
14722 } else {
14723 withoutHashUrl = '';
14724 if (isUndefined(withoutBaseUrl)) {
14725 appBase = url;
14726 /** @type {?} */ (this).replace();
14727 }
14728 }
14729 }
14730
14731 parseAppUrl(withoutHashUrl, this, false);
14732
14733 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
14734
14735 this.$$compose();
14736
14737 /*
14738 * In Windows, on an anchor node on documents loaded from
14739 * the filesystem, the browser will return a pathname
14740 * prefixed with the drive name ('/C:/path') when a
14741 * pathname without a drive is set:
14742 * * a.setAttribute('href', '/foo')
14743 * * a.pathname === '/C:/foo' //true
14744 *
14745 * Inside of AngularJS, we're always using pathnames that
14746 * do not include drive names for routing.
14747 */
14748 function removeWindowsDriveName(path, url, base) {
14749 /*
14750 Matches paths for file protocol on windows,
14751 such as /C:/foo/bar, and captures only /foo/bar.
14752 */
14753 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
14754
14755 var firstPathSegmentMatch;
14756
14757 //Get the relative path from the input URL.
14758 if (startsWith(url, base)) {
14759 url = url.replace(base, '');
14760 }
14761
14762 // The input URL intentionally contains a first path segment that ends with a colon.
14763 if (windowsFilePathExp.exec(url)) {
14764 return path;
14765 }
14766
14767 firstPathSegmentMatch = windowsFilePathExp.exec(path);
14768 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
14769 }
14770 };
14771
14772 this.$$normalizeUrl = function(url) {
14773 return appBase + (url ? hashPrefix + url : '');
14774 };
14775
14776 this.$$parseLinkUrl = function(url, relHref) {
14777 if (stripHash(appBase) === stripHash(url)) {
14778 this.$$parse(url);
14779 return true;
14780 }
14781 return false;
14782 };
14783}
14784
14785
14786/**
14787 * LocationHashbangUrl represents URL
14788 * This object is exposed as $location service when html5 history api is enabled but the browser
14789 * does not support it.
14790 *
14791 * @constructor
14792 * @param {string} appBase application base URL
14793 * @param {string} appBaseNoFile application base URL stripped of any filename
14794 * @param {string} hashPrefix hashbang prefix
14795 */
14796function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
14797 this.$$html5 = true;
14798 LocationHashbangUrl.apply(this, arguments);
14799
14800 this.$$parseLinkUrl = function(url, relHref) {
14801 if (relHref && relHref[0] === '#') {
14802 // special case for links to hash fragments:
14803 // keep the old url and only replace the hash fragment
14804 this.hash(relHref.slice(1));
14805 return true;
14806 }
14807
14808 var rewrittenUrl;
14809 var appUrl;
14810
14811 if (appBase === stripHash(url)) {
14812 rewrittenUrl = url;
14813 } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) {
14814 rewrittenUrl = appBase + hashPrefix + appUrl;
14815 } else if (appBaseNoFile === url + '/') {
14816 rewrittenUrl = appBaseNoFile;
14817 }
14818 if (rewrittenUrl) {
14819 this.$$parse(rewrittenUrl);
14820 }
14821 return !!rewrittenUrl;
14822 };
14823
14824 this.$$normalizeUrl = function(url) {
14825 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
14826 return appBase + hashPrefix + url;
14827 };
14828}
14829
14830
14831var locationPrototype = {
14832
14833 /**
14834 * Ensure absolute URL is initialized.
14835 * @private
14836 */
14837 $$absUrl:'',
14838
14839 /**
14840 * Are we in html5 mode?
14841 * @private
14842 */
14843 $$html5: false,
14844
14845 /**
14846 * Has any change been replacing?
14847 * @private
14848 */
14849 $$replace: false,
14850
14851 /**
14852 * Compose url and update `url` and `absUrl` property
14853 * @private
14854 */
14855 $$compose: function() {
14856 this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash);
14857 this.$$absUrl = this.$$normalizeUrl(this.$$url);
14858 this.$$urlUpdatedByLocation = true;
14859 },
14860
14861 /**
14862 * @ngdoc method
14863 * @name $location#absUrl
14864 *
14865 * @description
14866 * This method is getter only.
14867 *
14868 * Return full URL representation with all segments encoded according to rules specified in
14869 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
14870 *
14871 *
14872 * ```js
14873 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
14874 * var absUrl = $location.absUrl();
14875 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
14876 * ```
14877 *
14878 * @return {string} full URL
14879 */
14880 absUrl: locationGetter('$$absUrl'),
14881
14882 /**
14883 * @ngdoc method
14884 * @name $location#url
14885 *
14886 * @description
14887 * This method is getter / setter.
14888 *
14889 * Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
14890 *
14891 * Change path, search and hash, when called with parameter and return `$location`.
14892 *
14893 *
14894 * ```js
14895 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
14896 * var url = $location.url();
14897 * // => "/some/path?foo=bar&baz=xoxo"
14898 * ```
14899 *
14900 * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
14901 * @return {string} url
14902 */
14903 url: function(url) {
14904 if (isUndefined(url)) {
14905 return this.$$url;
14906 }
14907
14908 var match = PATH_MATCH.exec(url);
14909 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
14910 if (match[2] || match[1] || url === '') this.search(match[3] || '');
14911 this.hash(match[5] || '');
14912
14913 return this;
14914 },
14915
14916 /**
14917 * @ngdoc method
14918 * @name $location#protocol
14919 *
14920 * @description
14921 * This method is getter only.
14922 *
14923 * Return protocol of current URL.
14924 *
14925 *
14926 * ```js
14927 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
14928 * var protocol = $location.protocol();
14929 * // => "http"
14930 * ```
14931 *
14932 * @return {string} protocol of current URL
14933 */
14934 protocol: locationGetter('$$protocol'),
14935
14936 /**
14937 * @ngdoc method
14938 * @name $location#host
14939 *
14940 * @description
14941 * This method is getter only.
14942 *
14943 * Return host of current URL.
14944 *
14945 * Note: compared to the non-AngularJS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
14946 *
14947 *
14948 * ```js
14949 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
14950 * var host = $location.host();
14951 * // => "example.com"
14952 *
14953 * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
14954 * host = $location.host();
14955 * // => "example.com"
14956 * host = location.host;
14957 * // => "example.com:8080"
14958 * ```
14959 *
14960 * @return {string} host of current URL.
14961 */
14962 host: locationGetter('$$host'),
14963
14964 /**
14965 * @ngdoc method
14966 * @name $location#port
14967 *
14968 * @description
14969 * This method is getter only.
14970 *
14971 * Return port of current URL.
14972 *
14973 *
14974 * ```js
14975 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
14976 * var port = $location.port();
14977 * // => 80
14978 * ```
14979 *
14980 * @return {Number} port
14981 */
14982 port: locationGetter('$$port'),
14983
14984 /**
14985 * @ngdoc method
14986 * @name $location#path
14987 *
14988 * @description
14989 * This method is getter / setter.
14990 *
14991 * Return path of current URL when called without any parameter.
14992 *
14993 * Change path when called with parameter and return `$location`.
14994 *
14995 * Note: Path should always begin with forward slash (/), this method will add the forward slash
14996 * if it is missing.
14997 *
14998 *
14999 * ```js
15000 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
15001 * var path = $location.path();
15002 * // => "/some/path"
15003 * ```
15004 *
15005 * @param {(string|number)=} path New path
15006 * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
15007 */
15008 path: locationGetterSetter('$$path', function(path) {
15009 path = path !== null ? path.toString() : '';
15010 return path.charAt(0) === '/' ? path : '/' + path;
15011 }),
15012
15013 /**
15014 * @ngdoc method
15015 * @name $location#search
15016 *
15017 * @description
15018 * This method is getter / setter.
15019 *
15020 * Return search part (as object) of current URL when called without any parameter.
15021 *
15022 * Change search part when called with parameter and return `$location`.
15023 *
15024 *
15025 * ```js
15026 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
15027 * var searchObject = $location.search();
15028 * // => {foo: 'bar', baz: 'xoxo'}
15029 *
15030 * // set foo to 'yipee'
15031 * $location.search('foo', 'yipee');
15032 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
15033 * ```
15034 *
15035 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
15036 * hash object.
15037 *
15038 * When called with a single argument the method acts as a setter, setting the `search` component
15039 * of `$location` to the specified value.
15040 *
15041 * If the argument is a hash object containing an array of values, these values will be encoded
15042 * as duplicate search parameters in the URL.
15043 *
15044 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
15045 * will override only a single search property.
15046 *
15047 * If `paramValue` is an array, it will override the property of the `search` component of
15048 * `$location` specified via the first argument.
15049 *
15050 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
15051 *
15052 * If `paramValue` is `true`, the property specified via the first argument will be added with no
15053 * value nor trailing equal sign.
15054 *
15055 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
15056 * one or more arguments returns `$location` object itself.
15057 */
15058 search: function(search, paramValue) {
15059 switch (arguments.length) {
15060 case 0:
15061 return this.$$search;
15062 case 1:
15063 if (isString(search) || isNumber(search)) {
15064 search = search.toString();
15065 this.$$search = parseKeyValue(search);
15066 } else if (isObject(search)) {
15067 search = copy(search, {});
15068 // remove object undefined or null properties
15069 forEach(search, function(value, key) {
15070 if (value == null) delete search[key];
15071 });
15072
15073 this.$$search = search;
15074 } else {
15075 throw $locationMinErr('isrcharg',
15076 'The first argument of the `$location#search()` call must be a string or an object.');
15077 }
15078 break;
15079 default:
15080 if (isUndefined(paramValue) || paramValue === null) {
15081 delete this.$$search[search];
15082 } else {
15083 this.$$search[search] = paramValue;
15084 }
15085 }
15086
15087 this.$$compose();
15088 return this;
15089 },
15090
15091 /**
15092 * @ngdoc method
15093 * @name $location#hash
15094 *
15095 * @description
15096 * This method is getter / setter.
15097 *
15098 * Returns the hash fragment when called without any parameters.
15099 *
15100 * Changes the hash fragment when called with a parameter and returns `$location`.
15101 *
15102 *
15103 * ```js
15104 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
15105 * var hash = $location.hash();
15106 * // => "hashValue"
15107 * ```
15108 *
15109 * @param {(string|number)=} hash New hash fragment
15110 * @return {string} hash
15111 */
15112 hash: locationGetterSetter('$$hash', function(hash) {
15113 return hash !== null ? hash.toString() : '';
15114 }),
15115
15116 /**
15117 * @ngdoc method
15118 * @name $location#replace
15119 *
15120 * @description
15121 * If called, all changes to $location during the current `$digest` will replace the current history
15122 * record, instead of adding a new one.
15123 */
15124 replace: function() {
15125 this.$$replace = true;
15126 return this;
15127 }
15128};
15129
15130forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
15131 Location.prototype = Object.create(locationPrototype);
15132
15133 /**
15134 * @ngdoc method
15135 * @name $location#state
15136 *
15137 * @description
15138 * This method is getter / setter.
15139 *
15140 * Return the history state object when called without any parameter.
15141 *
15142 * Change the history state object when called with one parameter and return `$location`.
15143 * The state object is later passed to `pushState` or `replaceState`.
15144 *
15145 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
15146 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
15147 * older browsers (like IE9 or Android < 4.0), don't use this method.
15148 *
15149 * @param {object=} state State object for pushState or replaceState
15150 * @return {object} state
15151 */
15152 Location.prototype.state = function(state) {
15153 if (!arguments.length) {
15154 return this.$$state;
15155 }
15156
15157 if (Location !== LocationHtml5Url || !this.$$html5) {
15158 throw $locationMinErr('nostate', 'History API state support is available only ' +
15159 'in HTML5 mode and only in browsers supporting HTML5 History API');
15160 }
15161 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
15162 // but we're changing the $$state reference to $browser.state() during the $digest
15163 // so the modification window is narrow.
15164 this.$$state = isUndefined(state) ? null : state;
15165 this.$$urlUpdatedByLocation = true;
15166
15167 return this;
15168 };
15169});
15170
15171
15172function locationGetter(property) {
15173 return /** @this */ function() {
15174 return this[property];
15175 };
15176}
15177
15178
15179function locationGetterSetter(property, preprocess) {
15180 return /** @this */ function(value) {
15181 if (isUndefined(value)) {
15182 return this[property];
15183 }
15184
15185 this[property] = preprocess(value);
15186 this.$$compose();
15187
15188 return this;
15189 };
15190}
15191
15192
15193/**
15194 * @ngdoc service
15195 * @name $location
15196 *
15197 * @requires $rootElement
15198 *
15199 * @description
15200 * The $location service parses the URL in the browser address bar (based on the
15201 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
15202 * available to your application. Changes to the URL in the address bar are reflected into
15203 * $location service and changes to $location are reflected into the browser address bar.
15204 *
15205 * **The $location service:**
15206 *
15207 * - Exposes the current URL in the browser address bar, so you can
15208 * - Watch and observe the URL.
15209 * - Change the URL.
15210 * - Synchronizes the URL with the browser when the user
15211 * - Changes the address bar.
15212 * - Clicks the back or forward button (or clicks a History link).
15213 * - Clicks on a link.
15214 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
15215 *
15216 * For more information see {@link guide/$location Developer Guide: Using $location}
15217 */
15218
15219/**
15220 * @ngdoc provider
15221 * @name $locationProvider
15222 * @this
15223 *
15224 * @description
15225 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
15226 */
15227function $LocationProvider() {
15228 var hashPrefix = '!',
15229 html5Mode = {
15230 enabled: false,
15231 requireBase: true,
15232 rewriteLinks: true
15233 };
15234
15235 /**
15236 * @ngdoc method
15237 * @name $locationProvider#hashPrefix
15238 * @description
15239 * The default value for the prefix is `'!'`.
15240 * @param {string=} prefix Prefix for hash part (containing path and search)
15241 * @returns {*} current value if used as getter or itself (chaining) if used as setter
15242 */
15243 this.hashPrefix = function(prefix) {
15244 if (isDefined(prefix)) {
15245 hashPrefix = prefix;
15246 return this;
15247 } else {
15248 return hashPrefix;
15249 }
15250 };
15251
15252 /**
15253 * @ngdoc method
15254 * @name $locationProvider#html5Mode
15255 * @description
15256 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
15257 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
15258 * properties:
15259 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
15260 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
15261 * support `pushState`.
15262 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
15263 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
15264 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
15265 * See the {@link guide/$location $location guide for more information}
15266 * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled,
15267 * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will
15268 * only happen on links with an attribute that matches the given string. For example, if set
15269 * to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links.
15270 * Note that [attribute name normalization](guide/directive#normalization) does not apply
15271 * here, so `'internalLink'` will **not** match `'internal-link'`.
15272 *
15273 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
15274 */
15275 this.html5Mode = function(mode) {
15276 if (isBoolean(mode)) {
15277 html5Mode.enabled = mode;
15278 return this;
15279 } else if (isObject(mode)) {
15280
15281 if (isBoolean(mode.enabled)) {
15282 html5Mode.enabled = mode.enabled;
15283 }
15284
15285 if (isBoolean(mode.requireBase)) {
15286 html5Mode.requireBase = mode.requireBase;
15287 }
15288
15289 if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) {
15290 html5Mode.rewriteLinks = mode.rewriteLinks;
15291 }
15292
15293 return this;
15294 } else {
15295 return html5Mode;
15296 }
15297 };
15298
15299 /**
15300 * @ngdoc event
15301 * @name $location#$locationChangeStart
15302 * @eventType broadcast on root scope
15303 * @description
15304 * Broadcasted before a URL will change.
15305 *
15306 * This change can be prevented by calling
15307 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
15308 * details about event object. Upon successful change
15309 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
15310 *
15311 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
15312 * the browser supports the HTML5 History API.
15313 *
15314 * @param {Object} angularEvent Synthetic event object.
15315 * @param {string} newUrl New URL
15316 * @param {string=} oldUrl URL that was before it was changed.
15317 * @param {string=} newState New history state object
15318 * @param {string=} oldState History state object that was before it was changed.
15319 */
15320
15321 /**
15322 * @ngdoc event
15323 * @name $location#$locationChangeSuccess
15324 * @eventType broadcast on root scope
15325 * @description
15326 * Broadcasted after a URL was changed.
15327 *
15328 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
15329 * the browser supports the HTML5 History API.
15330 *
15331 * @param {Object} angularEvent Synthetic event object.
15332 * @param {string} newUrl New URL
15333 * @param {string=} oldUrl URL that was before it was changed.
15334 * @param {string=} newState New history state object
15335 * @param {string=} oldState History state object that was before it was changed.
15336 */
15337
15338 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
15339 function($rootScope, $browser, $sniffer, $rootElement, $window) {
15340 var $location,
15341 LocationMode,
15342 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
15343 initialUrl = $browser.url(),
15344 appBase;
15345
15346 if (html5Mode.enabled) {
15347 if (!baseHref && html5Mode.requireBase) {
15348 throw $locationMinErr('nobase',
15349 '$location in HTML5 mode requires a <base> tag to be present!');
15350 }
15351 appBase = serverBase(initialUrl) + (baseHref || '/');
15352 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
15353 } else {
15354 appBase = stripHash(initialUrl);
15355 LocationMode = LocationHashbangUrl;
15356 }
15357 var appBaseNoFile = stripFile(appBase);
15358
15359 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
15360 $location.$$parseLinkUrl(initialUrl, initialUrl);
15361
15362 $location.$$state = $browser.state();
15363
15364 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
15365
15366 // Determine if two URLs are equal despite potentially having different encoding/normalizing
15367 // such as $location.absUrl() vs $browser.url()
15368 // See https://github.com/angular/angular.js/issues/16592
15369 function urlsEqual(a, b) {
15370 return a === b || urlResolve(a).href === urlResolve(b).href;
15371 }
15372
15373 function setBrowserUrlWithFallback(url, replace, state) {
15374 var oldUrl = $location.url();
15375 var oldState = $location.$$state;
15376 try {
15377 $browser.url(url, replace, state);
15378
15379 // Make sure $location.state() returns referentially identical (not just deeply equal)
15380 // state object; this makes possible quick checking if the state changed in the digest
15381 // loop. Checking deep equality would be too expensive.
15382 $location.$$state = $browser.state();
15383 } catch (e) {
15384 // Restore old values if pushState fails
15385 $location.url(oldUrl);
15386 $location.$$state = oldState;
15387
15388 throw e;
15389 }
15390 }
15391
15392 $rootElement.on('click', function(event) {
15393 var rewriteLinks = html5Mode.rewriteLinks;
15394 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
15395 // currently we open nice url link and redirect then
15396
15397 if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return;
15398
15399 var elm = jqLite(event.target);
15400
15401 // traverse the DOM up to find first A tag
15402 while (nodeName_(elm[0]) !== 'a') {
15403 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
15404 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
15405 }
15406
15407 if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return;
15408
15409 var absHref = elm.prop('href');
15410 // get the actual href attribute - see
15411 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
15412 var relHref = elm.attr('href') || elm.attr('xlink:href');
15413
15414 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
15415 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
15416 // an animation.
15417 absHref = urlResolve(absHref.animVal).href;
15418 }
15419
15420 // Ignore when url is started with javascript: or mailto:
15421 if (IGNORE_URI_REGEXP.test(absHref)) return;
15422
15423 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
15424 if ($location.$$parseLinkUrl(absHref, relHref)) {
15425 // We do a preventDefault for all urls that are part of the AngularJS application,
15426 // in html5mode and also without, so that we are able to abort navigation without
15427 // getting double entries in the location history.
15428 event.preventDefault();
15429 // update location manually
15430 if ($location.absUrl() !== $browser.url()) {
15431 $rootScope.$apply();
15432 }
15433 }
15434 }
15435 });
15436
15437
15438 // rewrite hashbang url <> html5 url
15439 if ($location.absUrl() !== initialUrl) {
15440 $browser.url($location.absUrl(), true);
15441 }
15442
15443 var initializing = true;
15444
15445 // update $location when $browser url changes
15446 $browser.onUrlChange(function(newUrl, newState) {
15447
15448 if (!startsWith(newUrl, appBaseNoFile)) {
15449 // If we are navigating outside of the app then force a reload
15450 $window.location.href = newUrl;
15451 return;
15452 }
15453
15454 $rootScope.$evalAsync(function() {
15455 var oldUrl = $location.absUrl();
15456 var oldState = $location.$$state;
15457 var defaultPrevented;
15458 $location.$$parse(newUrl);
15459 $location.$$state = newState;
15460
15461 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
15462 newState, oldState).defaultPrevented;
15463
15464 // if the location was changed by a `$locationChangeStart` handler then stop
15465 // processing this location change
15466 if ($location.absUrl() !== newUrl) return;
15467
15468 if (defaultPrevented) {
15469 $location.$$parse(oldUrl);
15470 $location.$$state = oldState;
15471 setBrowserUrlWithFallback(oldUrl, false, oldState);
15472 } else {
15473 initializing = false;
15474 afterLocationChange(oldUrl, oldState);
15475 }
15476 });
15477 if (!$rootScope.$$phase) $rootScope.$digest();
15478 });
15479
15480 // update browser
15481 $rootScope.$watch(function $locationWatch() {
15482 if (initializing || $location.$$urlUpdatedByLocation) {
15483 $location.$$urlUpdatedByLocation = false;
15484
15485 var oldUrl = $browser.url();
15486 var newUrl = $location.absUrl();
15487 var oldState = $browser.state();
15488 var currentReplace = $location.$$replace;
15489 var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) ||
15490 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
15491
15492 if (initializing || urlOrStateChanged) {
15493 initializing = false;
15494
15495 $rootScope.$evalAsync(function() {
15496 var newUrl = $location.absUrl();
15497 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
15498 $location.$$state, oldState).defaultPrevented;
15499
15500 // if the location was changed by a `$locationChangeStart` handler then stop
15501 // processing this location change
15502 if ($location.absUrl() !== newUrl) return;
15503
15504 if (defaultPrevented) {
15505 $location.$$parse(oldUrl);
15506 $location.$$state = oldState;
15507 } else {
15508 if (urlOrStateChanged) {
15509 setBrowserUrlWithFallback(newUrl, currentReplace,
15510 oldState === $location.$$state ? null : $location.$$state);
15511 }
15512 afterLocationChange(oldUrl, oldState);
15513 }
15514 });
15515 }
15516 }
15517
15518 $location.$$replace = false;
15519
15520 // we don't need to return anything because $evalAsync will make the digest loop dirty when
15521 // there is a change
15522 });
15523
15524 return $location;
15525
15526 function afterLocationChange(oldUrl, oldState) {
15527 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
15528 $location.$$state, oldState);
15529 }
15530}];
15531}
15532
15533/**
15534 * @ngdoc service
15535 * @name $log
15536 * @requires $window
15537 *
15538 * @description
15539 * Simple service for logging. Default implementation safely writes the message
15540 * into the browser's console (if present).
15541 *
15542 * The main purpose of this service is to simplify debugging and troubleshooting.
15543 *
15544 * To reveal the location of the calls to `$log` in the JavaScript console,
15545 * you can "blackbox" the AngularJS source in your browser:
15546 *
15547 * [Mozilla description of blackboxing](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Black_box_a_source).
15548 * [Chrome description of blackboxing](https://developer.chrome.com/devtools/docs/blackboxing).
15549 *
15550 * Note: Not all browsers support blackboxing.
15551 *
15552 * The default is to log `debug` messages. You can use
15553 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
15554 *
15555 * @example
15556 <example module="logExample" name="log-service">
15557 <file name="script.js">
15558 angular.module('logExample', [])
15559 .controller('LogController', ['$scope', '$log', function($scope, $log) {
15560 $scope.$log = $log;
15561 $scope.message = 'Hello World!';
15562 }]);
15563 </file>
15564 <file name="index.html">
15565 <div ng-controller="LogController">
15566 <p>Reload this page with open console, enter text and hit the log button...</p>
15567 <label>Message:
15568 <input type="text" ng-model="message" /></label>
15569 <button ng-click="$log.log(message)">log</button>
15570 <button ng-click="$log.warn(message)">warn</button>
15571 <button ng-click="$log.info(message)">info</button>
15572 <button ng-click="$log.error(message)">error</button>
15573 <button ng-click="$log.debug(message)">debug</button>
15574 </div>
15575 </file>
15576 </example>
15577 */
15578
15579/**
15580 * @ngdoc provider
15581 * @name $logProvider
15582 * @this
15583 *
15584 * @description
15585 * Use the `$logProvider` to configure how the application logs messages
15586 */
15587function $LogProvider() {
15588 var debug = true,
15589 self = this;
15590
15591 /**
15592 * @ngdoc method
15593 * @name $logProvider#debugEnabled
15594 * @description
15595 * @param {boolean=} flag enable or disable debug level messages
15596 * @returns {*} current value if used as getter or itself (chaining) if used as setter
15597 */
15598 this.debugEnabled = function(flag) {
15599 if (isDefined(flag)) {
15600 debug = flag;
15601 return this;
15602 } else {
15603 return debug;
15604 }
15605 };
15606
15607 this.$get = ['$window', function($window) {
15608 // Support: IE 9-11, Edge 12-14+
15609 // IE/Edge display errors in such a way that it requires the user to click in 4 places
15610 // to see the stack trace. There is no way to feature-detect it so there's a chance
15611 // of the user agent sniffing to go wrong but since it's only about logging, this shouldn't
15612 // break apps. Other browsers display errors in a sensible way and some of them map stack
15613 // traces along source maps if available so it makes sense to let browsers display it
15614 // as they want.
15615 var formatStackTrace = msie || /\bEdge\//.test($window.navigator && $window.navigator.userAgent);
15616
15617 return {
15618 /**
15619 * @ngdoc method
15620 * @name $log#log
15621 *
15622 * @description
15623 * Write a log message
15624 */
15625 log: consoleLog('log'),
15626
15627 /**
15628 * @ngdoc method
15629 * @name $log#info
15630 *
15631 * @description
15632 * Write an information message
15633 */
15634 info: consoleLog('info'),
15635
15636 /**
15637 * @ngdoc method
15638 * @name $log#warn
15639 *
15640 * @description
15641 * Write a warning message
15642 */
15643 warn: consoleLog('warn'),
15644
15645 /**
15646 * @ngdoc method
15647 * @name $log#error
15648 *
15649 * @description
15650 * Write an error message
15651 */
15652 error: consoleLog('error'),
15653
15654 /**
15655 * @ngdoc method
15656 * @name $log#debug
15657 *
15658 * @description
15659 * Write a debug message
15660 */
15661 debug: (function() {
15662 var fn = consoleLog('debug');
15663
15664 return function() {
15665 if (debug) {
15666 fn.apply(self, arguments);
15667 }
15668 };
15669 })()
15670 };
15671
15672 function formatError(arg) {
15673 if (isError(arg)) {
15674 if (arg.stack && formatStackTrace) {
15675 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
15676 ? 'Error: ' + arg.message + '\n' + arg.stack
15677 : arg.stack;
15678 } else if (arg.sourceURL) {
15679 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
15680 }
15681 }
15682 return arg;
15683 }
15684
15685 function consoleLog(type) {
15686 var console = $window.console || {},
15687 logFn = console[type] || console.log || noop;
15688
15689 return function() {
15690 var args = [];
15691 forEach(arguments, function(arg) {
15692 args.push(formatError(arg));
15693 });
15694 // Support: IE 9 only
15695 // console methods don't inherit from Function.prototype in IE 9 so we can't
15696 // call `logFn.apply(console, args)` directly.
15697 return Function.prototype.apply.call(logFn, console, args);
15698 };
15699 }
15700 }];
15701}
15702
15703/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
15704 * Any commits to this file should be reviewed with security in mind. *
15705 * Changes to this file can potentially create security vulnerabilities. *
15706 * An approval from 2 Core members with history of modifying *
15707 * this file is required. *
15708 * *
15709 * Does the change somehow allow for arbitrary javascript to be executed? *
15710 * Or allows for someone to change the prototype of built-in objects? *
15711 * Or gives undesired access to variables likes document or window? *
15712 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
15713
15714var $parseMinErr = minErr('$parse');
15715
15716var objectValueOf = {}.constructor.prototype.valueOf;
15717
15718// Sandboxing AngularJS Expressions
15719// ------------------------------
15720// AngularJS expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by
15721// various means such as obtaining a reference to native JS functions like the Function constructor.
15722//
15723// As an example, consider the following AngularJS expression:
15724//
15725// {}.toString.constructor('alert("evil JS code")')
15726//
15727// It is important to realize that if you create an expression from a string that contains user provided
15728// content then it is possible that your application contains a security vulnerability to an XSS style attack.
15729//
15730// See https://docs.angularjs.org/guide/security
15731
15732
15733function getStringValue(name) {
15734 // Property names must be strings. This means that non-string objects cannot be used
15735 // as keys in an object. Any non-string object, including a number, is typecasted
15736 // into a string via the toString method.
15737 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names
15738 //
15739 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it
15740 // to a string. It's not always possible. If `name` is an object and its `toString` method is
15741 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown:
15742 //
15743 // TypeError: Cannot convert object to primitive value
15744 //
15745 // For performance reasons, we don't catch this error here and allow it to propagate up the call
15746 // stack. Note that you'll get the same error in JavaScript if you try to access a property using
15747 // such a 'broken' object as a key.
15748 return name + '';
15749}
15750
15751
15752var OPERATORS = createMap();
15753forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
15754var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'};
15755
15756
15757/////////////////////////////////////////
15758
15759
15760/**
15761 * @constructor
15762 */
15763var Lexer = function Lexer(options) {
15764 this.options = options;
15765};
15766
15767Lexer.prototype = {
15768 constructor: Lexer,
15769
15770 lex: function(text) {
15771 this.text = text;
15772 this.index = 0;
15773 this.tokens = [];
15774
15775 while (this.index < this.text.length) {
15776 var ch = this.text.charAt(this.index);
15777 if (ch === '"' || ch === '\'') {
15778 this.readString(ch);
15779 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
15780 this.readNumber();
15781 } else if (this.isIdentifierStart(this.peekMultichar())) {
15782 this.readIdent();
15783 } else if (this.is(ch, '(){}[].,;:?')) {
15784 this.tokens.push({index: this.index, text: ch});
15785 this.index++;
15786 } else if (this.isWhitespace(ch)) {
15787 this.index++;
15788 } else {
15789 var ch2 = ch + this.peek();
15790 var ch3 = ch2 + this.peek(2);
15791 var op1 = OPERATORS[ch];
15792 var op2 = OPERATORS[ch2];
15793 var op3 = OPERATORS[ch3];
15794 if (op1 || op2 || op3) {
15795 var token = op3 ? ch3 : (op2 ? ch2 : ch);
15796 this.tokens.push({index: this.index, text: token, operator: true});
15797 this.index += token.length;
15798 } else {
15799 this.throwError('Unexpected next character ', this.index, this.index + 1);
15800 }
15801 }
15802 }
15803 return this.tokens;
15804 },
15805
15806 is: function(ch, chars) {
15807 return chars.indexOf(ch) !== -1;
15808 },
15809
15810 peek: function(i) {
15811 var num = i || 1;
15812 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
15813 },
15814
15815 isNumber: function(ch) {
15816 return ('0' <= ch && ch <= '9') && typeof ch === 'string';
15817 },
15818
15819 isWhitespace: function(ch) {
15820 // IE treats non-breaking space as \u00A0
15821 return (ch === ' ' || ch === '\r' || ch === '\t' ||
15822 ch === '\n' || ch === '\v' || ch === '\u00A0');
15823 },
15824
15825 isIdentifierStart: function(ch) {
15826 return this.options.isIdentifierStart ?
15827 this.options.isIdentifierStart(ch, this.codePointAt(ch)) :
15828 this.isValidIdentifierStart(ch);
15829 },
15830
15831 isValidIdentifierStart: function(ch) {
15832 return ('a' <= ch && ch <= 'z' ||
15833 'A' <= ch && ch <= 'Z' ||
15834 '_' === ch || ch === '$');
15835 },
15836
15837 isIdentifierContinue: function(ch) {
15838 return this.options.isIdentifierContinue ?
15839 this.options.isIdentifierContinue(ch, this.codePointAt(ch)) :
15840 this.isValidIdentifierContinue(ch);
15841 },
15842
15843 isValidIdentifierContinue: function(ch, cp) {
15844 return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch);
15845 },
15846
15847 codePointAt: function(ch) {
15848 if (ch.length === 1) return ch.charCodeAt(0);
15849 // eslint-disable-next-line no-bitwise
15850 return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00;
15851 },
15852
15853 peekMultichar: function() {
15854 var ch = this.text.charAt(this.index);
15855 var peek = this.peek();
15856 if (!peek) {
15857 return ch;
15858 }
15859 var cp1 = ch.charCodeAt(0);
15860 var cp2 = peek.charCodeAt(0);
15861 if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
15862 return ch + peek;
15863 }
15864 return ch;
15865 },
15866
15867 isExpOperator: function(ch) {
15868 return (ch === '-' || ch === '+' || this.isNumber(ch));
15869 },
15870
15871 throwError: function(error, start, end) {
15872 end = end || this.index;
15873 var colStr = (isDefined(start)
15874 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
15875 : ' ' + end);
15876 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
15877 error, colStr, this.text);
15878 },
15879
15880 readNumber: function() {
15881 var number = '';
15882 var start = this.index;
15883 while (this.index < this.text.length) {
15884 var ch = lowercase(this.text.charAt(this.index));
15885 if (ch === '.' || this.isNumber(ch)) {
15886 number += ch;
15887 } else {
15888 var peekCh = this.peek();
15889 if (ch === 'e' && this.isExpOperator(peekCh)) {
15890 number += ch;
15891 } else if (this.isExpOperator(ch) &&
15892 peekCh && this.isNumber(peekCh) &&
15893 number.charAt(number.length - 1) === 'e') {
15894 number += ch;
15895 } else if (this.isExpOperator(ch) &&
15896 (!peekCh || !this.isNumber(peekCh)) &&
15897 number.charAt(number.length - 1) === 'e') {
15898 this.throwError('Invalid exponent');
15899 } else {
15900 break;
15901 }
15902 }
15903 this.index++;
15904 }
15905 this.tokens.push({
15906 index: start,
15907 text: number,
15908 constant: true,
15909 value: Number(number)
15910 });
15911 },
15912
15913 readIdent: function() {
15914 var start = this.index;
15915 this.index += this.peekMultichar().length;
15916 while (this.index < this.text.length) {
15917 var ch = this.peekMultichar();
15918 if (!this.isIdentifierContinue(ch)) {
15919 break;
15920 }
15921 this.index += ch.length;
15922 }
15923 this.tokens.push({
15924 index: start,
15925 text: this.text.slice(start, this.index),
15926 identifier: true
15927 });
15928 },
15929
15930 readString: function(quote) {
15931 var start = this.index;
15932 this.index++;
15933 var string = '';
15934 var rawString = quote;
15935 var escape = false;
15936 while (this.index < this.text.length) {
15937 var ch = this.text.charAt(this.index);
15938 rawString += ch;
15939 if (escape) {
15940 if (ch === 'u') {
15941 var hex = this.text.substring(this.index + 1, this.index + 5);
15942 if (!hex.match(/[\da-f]{4}/i)) {
15943 this.throwError('Invalid unicode escape [\\u' + hex + ']');
15944 }
15945 this.index += 4;
15946 string += String.fromCharCode(parseInt(hex, 16));
15947 } else {
15948 var rep = ESCAPE[ch];
15949 string = string + (rep || ch);
15950 }
15951 escape = false;
15952 } else if (ch === '\\') {
15953 escape = true;
15954 } else if (ch === quote) {
15955 this.index++;
15956 this.tokens.push({
15957 index: start,
15958 text: rawString,
15959 constant: true,
15960 value: string
15961 });
15962 return;
15963 } else {
15964 string += ch;
15965 }
15966 this.index++;
15967 }
15968 this.throwError('Unterminated quote', start);
15969 }
15970};
15971
15972var AST = function AST(lexer, options) {
15973 this.lexer = lexer;
15974 this.options = options;
15975};
15976
15977AST.Program = 'Program';
15978AST.ExpressionStatement = 'ExpressionStatement';
15979AST.AssignmentExpression = 'AssignmentExpression';
15980AST.ConditionalExpression = 'ConditionalExpression';
15981AST.LogicalExpression = 'LogicalExpression';
15982AST.BinaryExpression = 'BinaryExpression';
15983AST.UnaryExpression = 'UnaryExpression';
15984AST.CallExpression = 'CallExpression';
15985AST.MemberExpression = 'MemberExpression';
15986AST.Identifier = 'Identifier';
15987AST.Literal = 'Literal';
15988AST.ArrayExpression = 'ArrayExpression';
15989AST.Property = 'Property';
15990AST.ObjectExpression = 'ObjectExpression';
15991AST.ThisExpression = 'ThisExpression';
15992AST.LocalsExpression = 'LocalsExpression';
15993
15994// Internal use only
15995AST.NGValueParameter = 'NGValueParameter';
15996
15997AST.prototype = {
15998 ast: function(text) {
15999 this.text = text;
16000 this.tokens = this.lexer.lex(text);
16001
16002 var value = this.program();
16003
16004 if (this.tokens.length !== 0) {
16005 this.throwError('is an unexpected token', this.tokens[0]);
16006 }
16007
16008 return value;
16009 },
16010
16011 program: function() {
16012 var body = [];
16013 while (true) {
16014 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
16015 body.push(this.expressionStatement());
16016 if (!this.expect(';')) {
16017 return { type: AST.Program, body: body};
16018 }
16019 }
16020 },
16021
16022 expressionStatement: function() {
16023 return { type: AST.ExpressionStatement, expression: this.filterChain() };
16024 },
16025
16026 filterChain: function() {
16027 var left = this.expression();
16028 while (this.expect('|')) {
16029 left = this.filter(left);
16030 }
16031 return left;
16032 },
16033
16034 expression: function() {
16035 return this.assignment();
16036 },
16037
16038 assignment: function() {
16039 var result = this.ternary();
16040 if (this.expect('=')) {
16041 if (!isAssignable(result)) {
16042 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value');
16043 }
16044
16045 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
16046 }
16047 return result;
16048 },
16049
16050 ternary: function() {
16051 var test = this.logicalOR();
16052 var alternate;
16053 var consequent;
16054 if (this.expect('?')) {
16055 alternate = this.expression();
16056 if (this.consume(':')) {
16057 consequent = this.expression();
16058 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
16059 }
16060 }
16061 return test;
16062 },
16063
16064 logicalOR: function() {
16065 var left = this.logicalAND();
16066 while (this.expect('||')) {
16067 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
16068 }
16069 return left;
16070 },
16071
16072 logicalAND: function() {
16073 var left = this.equality();
16074 while (this.expect('&&')) {
16075 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
16076 }
16077 return left;
16078 },
16079
16080 equality: function() {
16081 var left = this.relational();
16082 var token;
16083 while ((token = this.expect('==','!=','===','!=='))) {
16084 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
16085 }
16086 return left;
16087 },
16088
16089 relational: function() {
16090 var left = this.additive();
16091 var token;
16092 while ((token = this.expect('<', '>', '<=', '>='))) {
16093 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
16094 }
16095 return left;
16096 },
16097
16098 additive: function() {
16099 var left = this.multiplicative();
16100 var token;
16101 while ((token = this.expect('+','-'))) {
16102 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
16103 }
16104 return left;
16105 },
16106
16107 multiplicative: function() {
16108 var left = this.unary();
16109 var token;
16110 while ((token = this.expect('*','/','%'))) {
16111 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
16112 }
16113 return left;
16114 },
16115
16116 unary: function() {
16117 var token;
16118 if ((token = this.expect('+', '-', '!'))) {
16119 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
16120 } else {
16121 return this.primary();
16122 }
16123 },
16124
16125 primary: function() {
16126 var primary;
16127 if (this.expect('(')) {
16128 primary = this.filterChain();
16129 this.consume(')');
16130 } else if (this.expect('[')) {
16131 primary = this.arrayDeclaration();
16132 } else if (this.expect('{')) {
16133 primary = this.object();
16134 } else if (this.selfReferential.hasOwnProperty(this.peek().text)) {
16135 primary = copy(this.selfReferential[this.consume().text]);
16136 } else if (this.options.literals.hasOwnProperty(this.peek().text)) {
16137 primary = { type: AST.Literal, value: this.options.literals[this.consume().text]};
16138 } else if (this.peek().identifier) {
16139 primary = this.identifier();
16140 } else if (this.peek().constant) {
16141 primary = this.constant();
16142 } else {
16143 this.throwError('not a primary expression', this.peek());
16144 }
16145
16146 var next;
16147 while ((next = this.expect('(', '[', '.'))) {
16148 if (next.text === '(') {
16149 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
16150 this.consume(')');
16151 } else if (next.text === '[') {
16152 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
16153 this.consume(']');
16154 } else if (next.text === '.') {
16155 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
16156 } else {
16157 this.throwError('IMPOSSIBLE');
16158 }
16159 }
16160 return primary;
16161 },
16162
16163 filter: function(baseExpression) {
16164 var args = [baseExpression];
16165 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
16166
16167 while (this.expect(':')) {
16168 args.push(this.expression());
16169 }
16170
16171 return result;
16172 },
16173
16174 parseArguments: function() {
16175 var args = [];
16176 if (this.peekToken().text !== ')') {
16177 do {
16178 args.push(this.filterChain());
16179 } while (this.expect(','));
16180 }
16181 return args;
16182 },
16183
16184 identifier: function() {
16185 var token = this.consume();
16186 if (!token.identifier) {
16187 this.throwError('is not a valid identifier', token);
16188 }
16189 return { type: AST.Identifier, name: token.text };
16190 },
16191
16192 constant: function() {
16193 // TODO check that it is a constant
16194 return { type: AST.Literal, value: this.consume().value };
16195 },
16196
16197 arrayDeclaration: function() {
16198 var elements = [];
16199 if (this.peekToken().text !== ']') {
16200 do {
16201 if (this.peek(']')) {
16202 // Support trailing commas per ES5.1.
16203 break;
16204 }
16205 elements.push(this.expression());
16206 } while (this.expect(','));
16207 }
16208 this.consume(']');
16209
16210 return { type: AST.ArrayExpression, elements: elements };
16211 },
16212
16213 object: function() {
16214 var properties = [], property;
16215 if (this.peekToken().text !== '}') {
16216 do {
16217 if (this.peek('}')) {
16218 // Support trailing commas per ES5.1.
16219 break;
16220 }
16221 property = {type: AST.Property, kind: 'init'};
16222 if (this.peek().constant) {
16223 property.key = this.constant();
16224 property.computed = false;
16225 this.consume(':');
16226 property.value = this.expression();
16227 } else if (this.peek().identifier) {
16228 property.key = this.identifier();
16229 property.computed = false;
16230 if (this.peek(':')) {
16231 this.consume(':');
16232 property.value = this.expression();
16233 } else {
16234 property.value = property.key;
16235 }
16236 } else if (this.peek('[')) {
16237 this.consume('[');
16238 property.key = this.expression();
16239 this.consume(']');
16240 property.computed = true;
16241 this.consume(':');
16242 property.value = this.expression();
16243 } else {
16244 this.throwError('invalid key', this.peek());
16245 }
16246 properties.push(property);
16247 } while (this.expect(','));
16248 }
16249 this.consume('}');
16250
16251 return {type: AST.ObjectExpression, properties: properties };
16252 },
16253
16254 throwError: function(msg, token) {
16255 throw $parseMinErr('syntax',
16256 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
16257 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
16258 },
16259
16260 consume: function(e1) {
16261 if (this.tokens.length === 0) {
16262 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
16263 }
16264
16265 var token = this.expect(e1);
16266 if (!token) {
16267 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
16268 }
16269 return token;
16270 },
16271
16272 peekToken: function() {
16273 if (this.tokens.length === 0) {
16274 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
16275 }
16276 return this.tokens[0];
16277 },
16278
16279 peek: function(e1, e2, e3, e4) {
16280 return this.peekAhead(0, e1, e2, e3, e4);
16281 },
16282
16283 peekAhead: function(i, e1, e2, e3, e4) {
16284 if (this.tokens.length > i) {
16285 var token = this.tokens[i];
16286 var t = token.text;
16287 if (t === e1 || t === e2 || t === e3 || t === e4 ||
16288 (!e1 && !e2 && !e3 && !e4)) {
16289 return token;
16290 }
16291 }
16292 return false;
16293 },
16294
16295 expect: function(e1, e2, e3, e4) {
16296 var token = this.peek(e1, e2, e3, e4);
16297 if (token) {
16298 this.tokens.shift();
16299 return token;
16300 }
16301 return false;
16302 },
16303
16304 selfReferential: {
16305 'this': {type: AST.ThisExpression },
16306 '$locals': {type: AST.LocalsExpression }
16307 }
16308};
16309
16310function ifDefined(v, d) {
16311 return typeof v !== 'undefined' ? v : d;
16312}
16313
16314function plusFn(l, r) {
16315 if (typeof l === 'undefined') return r;
16316 if (typeof r === 'undefined') return l;
16317 return l + r;
16318}
16319
16320function isStateless($filter, filterName) {
16321 var fn = $filter(filterName);
16322 return !fn.$stateful;
16323}
16324
16325var PURITY_ABSOLUTE = 1;
16326var PURITY_RELATIVE = 2;
16327
16328// Detect nodes which could depend on non-shallow state of objects
16329function isPure(node, parentIsPure) {
16330 switch (node.type) {
16331 // Computed members might invoke a stateful toString()
16332 case AST.MemberExpression:
16333 if (node.computed) {
16334 return false;
16335 }
16336 break;
16337
16338 // Unary always convert to primative
16339 case AST.UnaryExpression:
16340 return PURITY_ABSOLUTE;
16341
16342 // The binary + operator can invoke a stateful toString().
16343 case AST.BinaryExpression:
16344 return node.operator !== '+' ? PURITY_ABSOLUTE : false;
16345
16346 // Functions / filters probably read state from within objects
16347 case AST.CallExpression:
16348 return false;
16349 }
16350
16351 return (undefined === parentIsPure) ? PURITY_RELATIVE : parentIsPure;
16352}
16353
16354function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
16355 var allConstants;
16356 var argsToWatch;
16357 var isStatelessFilter;
16358
16359 var astIsPure = ast.isPure = isPure(ast, parentIsPure);
16360
16361 switch (ast.type) {
16362 case AST.Program:
16363 allConstants = true;
16364 forEach(ast.body, function(expr) {
16365 findConstantAndWatchExpressions(expr.expression, $filter, astIsPure);
16366 allConstants = allConstants && expr.expression.constant;
16367 });
16368 ast.constant = allConstants;
16369 break;
16370 case AST.Literal:
16371 ast.constant = true;
16372 ast.toWatch = [];
16373 break;
16374 case AST.UnaryExpression:
16375 findConstantAndWatchExpressions(ast.argument, $filter, astIsPure);
16376 ast.constant = ast.argument.constant;
16377 ast.toWatch = ast.argument.toWatch;
16378 break;
16379 case AST.BinaryExpression:
16380 findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
16381 findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
16382 ast.constant = ast.left.constant && ast.right.constant;
16383 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
16384 break;
16385 case AST.LogicalExpression:
16386 findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
16387 findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
16388 ast.constant = ast.left.constant && ast.right.constant;
16389 ast.toWatch = ast.constant ? [] : [ast];
16390 break;
16391 case AST.ConditionalExpression:
16392 findConstantAndWatchExpressions(ast.test, $filter, astIsPure);
16393 findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure);
16394 findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure);
16395 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
16396 ast.toWatch = ast.constant ? [] : [ast];
16397 break;
16398 case AST.Identifier:
16399 ast.constant = false;
16400 ast.toWatch = [ast];
16401 break;
16402 case AST.MemberExpression:
16403 findConstantAndWatchExpressions(ast.object, $filter, astIsPure);
16404 if (ast.computed) {
16405 findConstantAndWatchExpressions(ast.property, $filter, astIsPure);
16406 }
16407 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
16408 ast.toWatch = ast.constant ? [] : [ast];
16409 break;
16410 case AST.CallExpression:
16411 isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false;
16412 allConstants = isStatelessFilter;
16413 argsToWatch = [];
16414 forEach(ast.arguments, function(expr) {
16415 findConstantAndWatchExpressions(expr, $filter, astIsPure);
16416 allConstants = allConstants && expr.constant;
16417 argsToWatch.push.apply(argsToWatch, expr.toWatch);
16418 });
16419 ast.constant = allConstants;
16420 ast.toWatch = isStatelessFilter ? argsToWatch : [ast];
16421 break;
16422 case AST.AssignmentExpression:
16423 findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
16424 findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
16425 ast.constant = ast.left.constant && ast.right.constant;
16426 ast.toWatch = [ast];
16427 break;
16428 case AST.ArrayExpression:
16429 allConstants = true;
16430 argsToWatch = [];
16431 forEach(ast.elements, function(expr) {
16432 findConstantAndWatchExpressions(expr, $filter, astIsPure);
16433 allConstants = allConstants && expr.constant;
16434 argsToWatch.push.apply(argsToWatch, expr.toWatch);
16435 });
16436 ast.constant = allConstants;
16437 ast.toWatch = argsToWatch;
16438 break;
16439 case AST.ObjectExpression:
16440 allConstants = true;
16441 argsToWatch = [];
16442 forEach(ast.properties, function(property) {
16443 findConstantAndWatchExpressions(property.value, $filter, astIsPure);
16444 allConstants = allConstants && property.value.constant;
16445 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
16446 if (property.computed) {
16447 //`{[key]: value}` implicitly does `key.toString()` which may be non-pure
16448 findConstantAndWatchExpressions(property.key, $filter, /*parentIsPure=*/false);
16449 allConstants = allConstants && property.key.constant;
16450 argsToWatch.push.apply(argsToWatch, property.key.toWatch);
16451 }
16452 });
16453 ast.constant = allConstants;
16454 ast.toWatch = argsToWatch;
16455 break;
16456 case AST.ThisExpression:
16457 ast.constant = false;
16458 ast.toWatch = [];
16459 break;
16460 case AST.LocalsExpression:
16461 ast.constant = false;
16462 ast.toWatch = [];
16463 break;
16464 }
16465}
16466
16467function getInputs(body) {
16468 if (body.length !== 1) return;
16469 var lastExpression = body[0].expression;
16470 var candidate = lastExpression.toWatch;
16471 if (candidate.length !== 1) return candidate;
16472 return candidate[0] !== lastExpression ? candidate : undefined;
16473}
16474
16475function isAssignable(ast) {
16476 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
16477}
16478
16479function assignableAST(ast) {
16480 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
16481 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
16482 }
16483}
16484
16485function isLiteral(ast) {
16486 return ast.body.length === 0 ||
16487 ast.body.length === 1 && (
16488 ast.body[0].expression.type === AST.Literal ||
16489 ast.body[0].expression.type === AST.ArrayExpression ||
16490 ast.body[0].expression.type === AST.ObjectExpression);
16491}
16492
16493function isConstant(ast) {
16494 return ast.constant;
16495}
16496
16497function ASTCompiler($filter) {
16498 this.$filter = $filter;
16499}
16500
16501ASTCompiler.prototype = {
16502 compile: function(ast) {
16503 var self = this;
16504 this.state = {
16505 nextId: 0,
16506 filters: {},
16507 fn: {vars: [], body: [], own: {}},
16508 assign: {vars: [], body: [], own: {}},
16509 inputs: []
16510 };
16511 findConstantAndWatchExpressions(ast, self.$filter);
16512 var extra = '';
16513 var assignable;
16514 this.stage = 'assign';
16515 if ((assignable = assignableAST(ast))) {
16516 this.state.computing = 'assign';
16517 var result = this.nextId();
16518 this.recurse(assignable, result);
16519 this.return_(result);
16520 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
16521 }
16522 var toWatch = getInputs(ast.body);
16523 self.stage = 'inputs';
16524 forEach(toWatch, function(watch, key) {
16525 var fnKey = 'fn' + key;
16526 self.state[fnKey] = {vars: [], body: [], own: {}};
16527 self.state.computing = fnKey;
16528 var intoId = self.nextId();
16529 self.recurse(watch, intoId);
16530 self.return_(intoId);
16531 self.state.inputs.push({name: fnKey, isPure: watch.isPure});
16532 watch.watchId = key;
16533 });
16534 this.state.computing = 'fn';
16535 this.stage = 'main';
16536 this.recurse(ast);
16537 var fnString =
16538 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
16539 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
16540 '"' + this.USE + ' ' + this.STRICT + '";\n' +
16541 this.filterPrefix() +
16542 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
16543 extra +
16544 this.watchFns() +
16545 'return fn;';
16546
16547 // eslint-disable-next-line no-new-func
16548 var fn = (new Function('$filter',
16549 'getStringValue',
16550 'ifDefined',
16551 'plus',
16552 fnString))(
16553 this.$filter,
16554 getStringValue,
16555 ifDefined,
16556 plusFn);
16557 this.state = this.stage = undefined;
16558 return fn;
16559 },
16560
16561 USE: 'use',
16562
16563 STRICT: 'strict',
16564
16565 watchFns: function() {
16566 var result = [];
16567 var inputs = this.state.inputs;
16568 var self = this;
16569 forEach(inputs, function(input) {
16570 result.push('var ' + input.name + '=' + self.generateFunction(input.name, 's'));
16571 if (input.isPure) {
16572 result.push(input.name, '.isPure=' + JSON.stringify(input.isPure) + ';');
16573 }
16574 });
16575 if (inputs.length) {
16576 result.push('fn.inputs=[' + inputs.map(function(i) { return i.name; }).join(',') + '];');
16577 }
16578 return result.join('');
16579 },
16580
16581 generateFunction: function(name, params) {
16582 return 'function(' + params + '){' +
16583 this.varsPrefix(name) +
16584 this.body(name) +
16585 '};';
16586 },
16587
16588 filterPrefix: function() {
16589 var parts = [];
16590 var self = this;
16591 forEach(this.state.filters, function(id, filter) {
16592 parts.push(id + '=$filter(' + self.escape(filter) + ')');
16593 });
16594 if (parts.length) return 'var ' + parts.join(',') + ';';
16595 return '';
16596 },
16597
16598 varsPrefix: function(section) {
16599 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
16600 },
16601
16602 body: function(section) {
16603 return this.state[section].body.join('');
16604 },
16605
16606 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
16607 var left, right, self = this, args, expression, computed;
16608 recursionFn = recursionFn || noop;
16609 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
16610 intoId = intoId || this.nextId();
16611 this.if_('i',
16612 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
16613 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
16614 );
16615 return;
16616 }
16617 switch (ast.type) {
16618 case AST.Program:
16619 forEach(ast.body, function(expression, pos) {
16620 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
16621 if (pos !== ast.body.length - 1) {
16622 self.current().body.push(right, ';');
16623 } else {
16624 self.return_(right);
16625 }
16626 });
16627 break;
16628 case AST.Literal:
16629 expression = this.escape(ast.value);
16630 this.assign(intoId, expression);
16631 recursionFn(intoId || expression);
16632 break;
16633 case AST.UnaryExpression:
16634 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
16635 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
16636 this.assign(intoId, expression);
16637 recursionFn(expression);
16638 break;
16639 case AST.BinaryExpression:
16640 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
16641 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
16642 if (ast.operator === '+') {
16643 expression = this.plus(left, right);
16644 } else if (ast.operator === '-') {
16645 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
16646 } else {
16647 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
16648 }
16649 this.assign(intoId, expression);
16650 recursionFn(expression);
16651 break;
16652 case AST.LogicalExpression:
16653 intoId = intoId || this.nextId();
16654 self.recurse(ast.left, intoId);
16655 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
16656 recursionFn(intoId);
16657 break;
16658 case AST.ConditionalExpression:
16659 intoId = intoId || this.nextId();
16660 self.recurse(ast.test, intoId);
16661 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
16662 recursionFn(intoId);
16663 break;
16664 case AST.Identifier:
16665 intoId = intoId || this.nextId();
16666 if (nameId) {
16667 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
16668 nameId.computed = false;
16669 nameId.name = ast.name;
16670 }
16671 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
16672 function() {
16673 self.if_(self.stage === 'inputs' || 's', function() {
16674 if (create && create !== 1) {
16675 self.if_(
16676 self.isNull(self.nonComputedMember('s', ast.name)),
16677 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
16678 }
16679 self.assign(intoId, self.nonComputedMember('s', ast.name));
16680 });
16681 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
16682 );
16683 recursionFn(intoId);
16684 break;
16685 case AST.MemberExpression:
16686 left = nameId && (nameId.context = this.nextId()) || this.nextId();
16687 intoId = intoId || this.nextId();
16688 self.recurse(ast.object, left, undefined, function() {
16689 self.if_(self.notNull(left), function() {
16690 if (ast.computed) {
16691 right = self.nextId();
16692 self.recurse(ast.property, right);
16693 self.getStringValue(right);
16694 if (create && create !== 1) {
16695 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
16696 }
16697 expression = self.computedMember(left, right);
16698 self.assign(intoId, expression);
16699 if (nameId) {
16700 nameId.computed = true;
16701 nameId.name = right;
16702 }
16703 } else {
16704 if (create && create !== 1) {
16705 self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
16706 }
16707 expression = self.nonComputedMember(left, ast.property.name);
16708 self.assign(intoId, expression);
16709 if (nameId) {
16710 nameId.computed = false;
16711 nameId.name = ast.property.name;
16712 }
16713 }
16714 }, function() {
16715 self.assign(intoId, 'undefined');
16716 });
16717 recursionFn(intoId);
16718 }, !!create);
16719 break;
16720 case AST.CallExpression:
16721 intoId = intoId || this.nextId();
16722 if (ast.filter) {
16723 right = self.filter(ast.callee.name);
16724 args = [];
16725 forEach(ast.arguments, function(expr) {
16726 var argument = self.nextId();
16727 self.recurse(expr, argument);
16728 args.push(argument);
16729 });
16730 expression = right + '(' + args.join(',') + ')';
16731 self.assign(intoId, expression);
16732 recursionFn(intoId);
16733 } else {
16734 right = self.nextId();
16735 left = {};
16736 args = [];
16737 self.recurse(ast.callee, right, left, function() {
16738 self.if_(self.notNull(right), function() {
16739 forEach(ast.arguments, function(expr) {
16740 self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
16741 args.push(argument);
16742 });
16743 });
16744 if (left.name) {
16745 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
16746 } else {
16747 expression = right + '(' + args.join(',') + ')';
16748 }
16749 self.assign(intoId, expression);
16750 }, function() {
16751 self.assign(intoId, 'undefined');
16752 });
16753 recursionFn(intoId);
16754 });
16755 }
16756 break;
16757 case AST.AssignmentExpression:
16758 right = this.nextId();
16759 left = {};
16760 this.recurse(ast.left, undefined, left, function() {
16761 self.if_(self.notNull(left.context), function() {
16762 self.recurse(ast.right, right);
16763 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
16764 self.assign(intoId, expression);
16765 recursionFn(intoId || expression);
16766 });
16767 }, 1);
16768 break;
16769 case AST.ArrayExpression:
16770 args = [];
16771 forEach(ast.elements, function(expr) {
16772 self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
16773 args.push(argument);
16774 });
16775 });
16776 expression = '[' + args.join(',') + ']';
16777 this.assign(intoId, expression);
16778 recursionFn(intoId || expression);
16779 break;
16780 case AST.ObjectExpression:
16781 args = [];
16782 computed = false;
16783 forEach(ast.properties, function(property) {
16784 if (property.computed) {
16785 computed = true;
16786 }
16787 });
16788 if (computed) {
16789 intoId = intoId || this.nextId();
16790 this.assign(intoId, '{}');
16791 forEach(ast.properties, function(property) {
16792 if (property.computed) {
16793 left = self.nextId();
16794 self.recurse(property.key, left);
16795 } else {
16796 left = property.key.type === AST.Identifier ?
16797 property.key.name :
16798 ('' + property.key.value);
16799 }
16800 right = self.nextId();
16801 self.recurse(property.value, right);
16802 self.assign(self.member(intoId, left, property.computed), right);
16803 });
16804 } else {
16805 forEach(ast.properties, function(property) {
16806 self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) {
16807 args.push(self.escape(
16808 property.key.type === AST.Identifier ? property.key.name :
16809 ('' + property.key.value)) +
16810 ':' + expr);
16811 });
16812 });
16813 expression = '{' + args.join(',') + '}';
16814 this.assign(intoId, expression);
16815 }
16816 recursionFn(intoId || expression);
16817 break;
16818 case AST.ThisExpression:
16819 this.assign(intoId, 's');
16820 recursionFn(intoId || 's');
16821 break;
16822 case AST.LocalsExpression:
16823 this.assign(intoId, 'l');
16824 recursionFn(intoId || 'l');
16825 break;
16826 case AST.NGValueParameter:
16827 this.assign(intoId, 'v');
16828 recursionFn(intoId || 'v');
16829 break;
16830 }
16831 },
16832
16833 getHasOwnProperty: function(element, property) {
16834 var key = element + '.' + property;
16835 var own = this.current().own;
16836 if (!own.hasOwnProperty(key)) {
16837 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
16838 }
16839 return own[key];
16840 },
16841
16842 assign: function(id, value) {
16843 if (!id) return;
16844 this.current().body.push(id, '=', value, ';');
16845 return id;
16846 },
16847
16848 filter: function(filterName) {
16849 if (!this.state.filters.hasOwnProperty(filterName)) {
16850 this.state.filters[filterName] = this.nextId(true);
16851 }
16852 return this.state.filters[filterName];
16853 },
16854
16855 ifDefined: function(id, defaultValue) {
16856 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
16857 },
16858
16859 plus: function(left, right) {
16860 return 'plus(' + left + ',' + right + ')';
16861 },
16862
16863 return_: function(id) {
16864 this.current().body.push('return ', id, ';');
16865 },
16866
16867 if_: function(test, alternate, consequent) {
16868 if (test === true) {
16869 alternate();
16870 } else {
16871 var body = this.current().body;
16872 body.push('if(', test, '){');
16873 alternate();
16874 body.push('}');
16875 if (consequent) {
16876 body.push('else{');
16877 consequent();
16878 body.push('}');
16879 }
16880 }
16881 },
16882
16883 not: function(expression) {
16884 return '!(' + expression + ')';
16885 },
16886
16887 isNull: function(expression) {
16888 return expression + '==null';
16889 },
16890
16891 notNull: function(expression) {
16892 return expression + '!=null';
16893 },
16894
16895 nonComputedMember: function(left, right) {
16896 var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
16897 var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g;
16898 if (SAFE_IDENTIFIER.test(right)) {
16899 return left + '.' + right;
16900 } else {
16901 return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]';
16902 }
16903 },
16904
16905 computedMember: function(left, right) {
16906 return left + '[' + right + ']';
16907 },
16908
16909 member: function(left, right, computed) {
16910 if (computed) return this.computedMember(left, right);
16911 return this.nonComputedMember(left, right);
16912 },
16913
16914 getStringValue: function(item) {
16915 this.assign(item, 'getStringValue(' + item + ')');
16916 },
16917
16918 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
16919 var self = this;
16920 return function() {
16921 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
16922 };
16923 },
16924
16925 lazyAssign: function(id, value) {
16926 var self = this;
16927 return function() {
16928 self.assign(id, value);
16929 };
16930 },
16931
16932 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
16933
16934 stringEscapeFn: function(c) {
16935 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
16936 },
16937
16938 escape: function(value) {
16939 if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\'';
16940 if (isNumber(value)) return value.toString();
16941 if (value === true) return 'true';
16942 if (value === false) return 'false';
16943 if (value === null) return 'null';
16944 if (typeof value === 'undefined') return 'undefined';
16945
16946 throw $parseMinErr('esc', 'IMPOSSIBLE');
16947 },
16948
16949 nextId: function(skip, init) {
16950 var id = 'v' + (this.state.nextId++);
16951 if (!skip) {
16952 this.current().vars.push(id + (init ? '=' + init : ''));
16953 }
16954 return id;
16955 },
16956
16957 current: function() {
16958 return this.state[this.state.computing];
16959 }
16960};
16961
16962
16963function ASTInterpreter($filter) {
16964 this.$filter = $filter;
16965}
16966
16967ASTInterpreter.prototype = {
16968 compile: function(ast) {
16969 var self = this;
16970 findConstantAndWatchExpressions(ast, self.$filter);
16971 var assignable;
16972 var assign;
16973 if ((assignable = assignableAST(ast))) {
16974 assign = this.recurse(assignable);
16975 }
16976 var toWatch = getInputs(ast.body);
16977 var inputs;
16978 if (toWatch) {
16979 inputs = [];
16980 forEach(toWatch, function(watch, key) {
16981 var input = self.recurse(watch);
16982 input.isPure = watch.isPure;
16983 watch.input = input;
16984 inputs.push(input);
16985 watch.watchId = key;
16986 });
16987 }
16988 var expressions = [];
16989 forEach(ast.body, function(expression) {
16990 expressions.push(self.recurse(expression.expression));
16991 });
16992 var fn = ast.body.length === 0 ? noop :
16993 ast.body.length === 1 ? expressions[0] :
16994 function(scope, locals) {
16995 var lastValue;
16996 forEach(expressions, function(exp) {
16997 lastValue = exp(scope, locals);
16998 });
16999 return lastValue;
17000 };
17001 if (assign) {
17002 fn.assign = function(scope, value, locals) {
17003 return assign(scope, locals, value);
17004 };
17005 }
17006 if (inputs) {
17007 fn.inputs = inputs;
17008 }
17009 return fn;
17010 },
17011
17012 recurse: function(ast, context, create) {
17013 var left, right, self = this, args;
17014 if (ast.input) {
17015 return this.inputs(ast.input, ast.watchId);
17016 }
17017 switch (ast.type) {
17018 case AST.Literal:
17019 return this.value(ast.value, context);
17020 case AST.UnaryExpression:
17021 right = this.recurse(ast.argument);
17022 return this['unary' + ast.operator](right, context);
17023 case AST.BinaryExpression:
17024 left = this.recurse(ast.left);
17025 right = this.recurse(ast.right);
17026 return this['binary' + ast.operator](left, right, context);
17027 case AST.LogicalExpression:
17028 left = this.recurse(ast.left);
17029 right = this.recurse(ast.right);
17030 return this['binary' + ast.operator](left, right, context);
17031 case AST.ConditionalExpression:
17032 return this['ternary?:'](
17033 this.recurse(ast.test),
17034 this.recurse(ast.alternate),
17035 this.recurse(ast.consequent),
17036 context
17037 );
17038 case AST.Identifier:
17039 return self.identifier(ast.name, context, create);
17040 case AST.MemberExpression:
17041 left = this.recurse(ast.object, false, !!create);
17042 if (!ast.computed) {
17043 right = ast.property.name;
17044 }
17045 if (ast.computed) right = this.recurse(ast.property);
17046 return ast.computed ?
17047 this.computedMember(left, right, context, create) :
17048 this.nonComputedMember(left, right, context, create);
17049 case AST.CallExpression:
17050 args = [];
17051 forEach(ast.arguments, function(expr) {
17052 args.push(self.recurse(expr));
17053 });
17054 if (ast.filter) right = this.$filter(ast.callee.name);
17055 if (!ast.filter) right = this.recurse(ast.callee, true);
17056 return ast.filter ?
17057 function(scope, locals, assign, inputs) {
17058 var values = [];
17059 for (var i = 0; i < args.length; ++i) {
17060 values.push(args[i](scope, locals, assign, inputs));
17061 }
17062 var value = right.apply(undefined, values, inputs);
17063 return context ? {context: undefined, name: undefined, value: value} : value;
17064 } :
17065 function(scope, locals, assign, inputs) {
17066 var rhs = right(scope, locals, assign, inputs);
17067 var value;
17068 if (rhs.value != null) {
17069 var values = [];
17070 for (var i = 0; i < args.length; ++i) {
17071 values.push(args[i](scope, locals, assign, inputs));
17072 }
17073 value = rhs.value.apply(rhs.context, values);
17074 }
17075 return context ? {value: value} : value;
17076 };
17077 case AST.AssignmentExpression:
17078 left = this.recurse(ast.left, true, 1);
17079 right = this.recurse(ast.right);
17080 return function(scope, locals, assign, inputs) {
17081 var lhs = left(scope, locals, assign, inputs);
17082 var rhs = right(scope, locals, assign, inputs);
17083 lhs.context[lhs.name] = rhs;
17084 return context ? {value: rhs} : rhs;
17085 };
17086 case AST.ArrayExpression:
17087 args = [];
17088 forEach(ast.elements, function(expr) {
17089 args.push(self.recurse(expr));
17090 });
17091 return function(scope, locals, assign, inputs) {
17092 var value = [];
17093 for (var i = 0; i < args.length; ++i) {
17094 value.push(args[i](scope, locals, assign, inputs));
17095 }
17096 return context ? {value: value} : value;
17097 };
17098 case AST.ObjectExpression:
17099 args = [];
17100 forEach(ast.properties, function(property) {
17101 if (property.computed) {
17102 args.push({key: self.recurse(property.key),
17103 computed: true,
17104 value: self.recurse(property.value)
17105 });
17106 } else {
17107 args.push({key: property.key.type === AST.Identifier ?
17108 property.key.name :
17109 ('' + property.key.value),
17110 computed: false,
17111 value: self.recurse(property.value)
17112 });
17113 }
17114 });
17115 return function(scope, locals, assign, inputs) {
17116 var value = {};
17117 for (var i = 0; i < args.length; ++i) {
17118 if (args[i].computed) {
17119 value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs);
17120 } else {
17121 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
17122 }
17123 }
17124 return context ? {value: value} : value;
17125 };
17126 case AST.ThisExpression:
17127 return function(scope) {
17128 return context ? {value: scope} : scope;
17129 };
17130 case AST.LocalsExpression:
17131 return function(scope, locals) {
17132 return context ? {value: locals} : locals;
17133 };
17134 case AST.NGValueParameter:
17135 return function(scope, locals, assign) {
17136 return context ? {value: assign} : assign;
17137 };
17138 }
17139 },
17140
17141 'unary+': function(argument, context) {
17142 return function(scope, locals, assign, inputs) {
17143 var arg = argument(scope, locals, assign, inputs);
17144 if (isDefined(arg)) {
17145 arg = +arg;
17146 } else {
17147 arg = 0;
17148 }
17149 return context ? {value: arg} : arg;
17150 };
17151 },
17152 'unary-': function(argument, context) {
17153 return function(scope, locals, assign, inputs) {
17154 var arg = argument(scope, locals, assign, inputs);
17155 if (isDefined(arg)) {
17156 arg = -arg;
17157 } else {
17158 arg = -0;
17159 }
17160 return context ? {value: arg} : arg;
17161 };
17162 },
17163 'unary!': function(argument, context) {
17164 return function(scope, locals, assign, inputs) {
17165 var arg = !argument(scope, locals, assign, inputs);
17166 return context ? {value: arg} : arg;
17167 };
17168 },
17169 'binary+': function(left, right, context) {
17170 return function(scope, locals, assign, inputs) {
17171 var lhs = left(scope, locals, assign, inputs);
17172 var rhs = right(scope, locals, assign, inputs);
17173 var arg = plusFn(lhs, rhs);
17174 return context ? {value: arg} : arg;
17175 };
17176 },
17177 'binary-': function(left, right, context) {
17178 return function(scope, locals, assign, inputs) {
17179 var lhs = left(scope, locals, assign, inputs);
17180 var rhs = right(scope, locals, assign, inputs);
17181 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
17182 return context ? {value: arg} : arg;
17183 };
17184 },
17185 'binary*': function(left, right, context) {
17186 return function(scope, locals, assign, inputs) {
17187 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
17188 return context ? {value: arg} : arg;
17189 };
17190 },
17191 'binary/': function(left, right, context) {
17192 return function(scope, locals, assign, inputs) {
17193 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
17194 return context ? {value: arg} : arg;
17195 };
17196 },
17197 'binary%': function(left, right, context) {
17198 return function(scope, locals, assign, inputs) {
17199 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
17200 return context ? {value: arg} : arg;
17201 };
17202 },
17203 'binary===': function(left, right, context) {
17204 return function(scope, locals, assign, inputs) {
17205 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
17206 return context ? {value: arg} : arg;
17207 };
17208 },
17209 'binary!==': function(left, right, context) {
17210 return function(scope, locals, assign, inputs) {
17211 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
17212 return context ? {value: arg} : arg;
17213 };
17214 },
17215 'binary==': function(left, right, context) {
17216 return function(scope, locals, assign, inputs) {
17217 // eslint-disable-next-line eqeqeq
17218 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
17219 return context ? {value: arg} : arg;
17220 };
17221 },
17222 'binary!=': function(left, right, context) {
17223 return function(scope, locals, assign, inputs) {
17224 // eslint-disable-next-line eqeqeq
17225 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
17226 return context ? {value: arg} : arg;
17227 };
17228 },
17229 'binary<': function(left, right, context) {
17230 return function(scope, locals, assign, inputs) {
17231 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
17232 return context ? {value: arg} : arg;
17233 };
17234 },
17235 'binary>': function(left, right, context) {
17236 return function(scope, locals, assign, inputs) {
17237 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
17238 return context ? {value: arg} : arg;
17239 };
17240 },
17241 'binary<=': function(left, right, context) {
17242 return function(scope, locals, assign, inputs) {
17243 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
17244 return context ? {value: arg} : arg;
17245 };
17246 },
17247 'binary>=': function(left, right, context) {
17248 return function(scope, locals, assign, inputs) {
17249 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
17250 return context ? {value: arg} : arg;
17251 };
17252 },
17253 'binary&&': function(left, right, context) {
17254 return function(scope, locals, assign, inputs) {
17255 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
17256 return context ? {value: arg} : arg;
17257 };
17258 },
17259 'binary||': function(left, right, context) {
17260 return function(scope, locals, assign, inputs) {
17261 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
17262 return context ? {value: arg} : arg;
17263 };
17264 },
17265 'ternary?:': function(test, alternate, consequent, context) {
17266 return function(scope, locals, assign, inputs) {
17267 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
17268 return context ? {value: arg} : arg;
17269 };
17270 },
17271 value: function(value, context) {
17272 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
17273 },
17274 identifier: function(name, context, create) {
17275 return function(scope, locals, assign, inputs) {
17276 var base = locals && (name in locals) ? locals : scope;
17277 if (create && create !== 1 && base && base[name] == null) {
17278 base[name] = {};
17279 }
17280 var value = base ? base[name] : undefined;
17281 if (context) {
17282 return {context: base, name: name, value: value};
17283 } else {
17284 return value;
17285 }
17286 };
17287 },
17288 computedMember: function(left, right, context, create) {
17289 return function(scope, locals, assign, inputs) {
17290 var lhs = left(scope, locals, assign, inputs);
17291 var rhs;
17292 var value;
17293 if (lhs != null) {
17294 rhs = right(scope, locals, assign, inputs);
17295 rhs = getStringValue(rhs);
17296 if (create && create !== 1) {
17297 if (lhs && !(lhs[rhs])) {
17298 lhs[rhs] = {};
17299 }
17300 }
17301 value = lhs[rhs];
17302 }
17303 if (context) {
17304 return {context: lhs, name: rhs, value: value};
17305 } else {
17306 return value;
17307 }
17308 };
17309 },
17310 nonComputedMember: function(left, right, context, create) {
17311 return function(scope, locals, assign, inputs) {
17312 var lhs = left(scope, locals, assign, inputs);
17313 if (create && create !== 1) {
17314 if (lhs && lhs[right] == null) {
17315 lhs[right] = {};
17316 }
17317 }
17318 var value = lhs != null ? lhs[right] : undefined;
17319 if (context) {
17320 return {context: lhs, name: right, value: value};
17321 } else {
17322 return value;
17323 }
17324 };
17325 },
17326 inputs: function(input, watchId) {
17327 return function(scope, value, locals, inputs) {
17328 if (inputs) return inputs[watchId];
17329 return input(scope, value, locals);
17330 };
17331 }
17332};
17333
17334/**
17335 * @constructor
17336 */
17337function Parser(lexer, $filter, options) {
17338 this.ast = new AST(lexer, options);
17339 this.astCompiler = options.csp ? new ASTInterpreter($filter) :
17340 new ASTCompiler($filter);
17341}
17342
17343Parser.prototype = {
17344 constructor: Parser,
17345
17346 parse: function(text) {
17347 var ast = this.getAst(text);
17348 var fn = this.astCompiler.compile(ast.ast);
17349 fn.literal = isLiteral(ast.ast);
17350 fn.constant = isConstant(ast.ast);
17351 fn.oneTime = ast.oneTime;
17352 return fn;
17353 },
17354
17355 getAst: function(exp) {
17356 var oneTime = false;
17357 exp = exp.trim();
17358
17359 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
17360 oneTime = true;
17361 exp = exp.substring(2);
17362 }
17363 return {
17364 ast: this.ast.ast(exp),
17365 oneTime: oneTime
17366 };
17367 }
17368};
17369
17370function getValueOf(value) {
17371 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
17372}
17373
17374///////////////////////////////////
17375
17376/**
17377 * @ngdoc service
17378 * @name $parse
17379 * @kind function
17380 *
17381 * @description
17382 *
17383 * Converts AngularJS {@link guide/expression expression} into a function.
17384 *
17385 * ```js
17386 * var getter = $parse('user.name');
17387 * var setter = getter.assign;
17388 * var context = {user:{name:'AngularJS'}};
17389 * var locals = {user:{name:'local'}};
17390 *
17391 * expect(getter(context)).toEqual('AngularJS');
17392 * setter(context, 'newValue');
17393 * expect(context.user.name).toEqual('newValue');
17394 * expect(getter(context, locals)).toEqual('local');
17395 * ```
17396 *
17397 *
17398 * @param {string} expression String expression to compile.
17399 * @returns {function(context, locals)} a function which represents the compiled expression:
17400 *
17401 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17402 * are evaluated against (typically a scope object).
17403 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17404 * `context`.
17405 *
17406 * The returned function also has the following properties:
17407 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
17408 * literal.
17409 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
17410 * constant literals.
17411 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
17412 * set to a function to change its value on the given context.
17413 *
17414 */
17415
17416
17417/**
17418 * @ngdoc provider
17419 * @name $parseProvider
17420 * @this
17421 *
17422 * @description
17423 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
17424 * service.
17425 */
17426function $ParseProvider() {
17427 var cache = createMap();
17428 var literals = {
17429 'true': true,
17430 'false': false,
17431 'null': null,
17432 'undefined': undefined
17433 };
17434 var identStart, identContinue;
17435
17436 /**
17437 * @ngdoc method
17438 * @name $parseProvider#addLiteral
17439 * @description
17440 *
17441 * Configure $parse service to add literal values that will be present as literal at expressions.
17442 *
17443 * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name.
17444 * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`.
17445 *
17446 **/
17447 this.addLiteral = function(literalName, literalValue) {
17448 literals[literalName] = literalValue;
17449 };
17450
17451 /**
17452 * @ngdoc method
17453 * @name $parseProvider#setIdentifierFns
17454 *
17455 * @description
17456 *
17457 * Allows defining the set of characters that are allowed in AngularJS expressions. The function
17458 * `identifierStart` will get called to know if a given character is a valid character to be the
17459 * first character for an identifier. The function `identifierContinue` will get called to know if
17460 * a given character is a valid character to be a follow-up identifier character. The functions
17461 * `identifierStart` and `identifierContinue` will receive as arguments the single character to be
17462 * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
17463 * mind that the `string` parameter can be two characters long depending on the character
17464 * representation. It is expected for the function to return `true` or `false`, whether that
17465 * character is allowed or not.
17466 *
17467 * Since this function will be called extensively, keep the implementation of these functions fast,
17468 * as the performance of these functions have a direct impact on the expressions parsing speed.
17469 *
17470 * @param {function=} identifierStart The function that will decide whether the given character is
17471 * a valid identifier start character.
17472 * @param {function=} identifierContinue The function that will decide whether the given character is
17473 * a valid identifier continue character.
17474 */
17475 this.setIdentifierFns = function(identifierStart, identifierContinue) {
17476 identStart = identifierStart;
17477 identContinue = identifierContinue;
17478 return this;
17479 };
17480
17481 this.$get = ['$filter', function($filter) {
17482 var noUnsafeEval = csp().noUnsafeEval;
17483 var $parseOptions = {
17484 csp: noUnsafeEval,
17485 literals: copy(literals),
17486 isIdentifierStart: isFunction(identStart) && identStart,
17487 isIdentifierContinue: isFunction(identContinue) && identContinue
17488 };
17489 $parse.$$getAst = $$getAst;
17490 return $parse;
17491
17492 function $parse(exp, interceptorFn) {
17493 var parsedExpression, cacheKey;
17494
17495 switch (typeof exp) {
17496 case 'string':
17497 exp = exp.trim();
17498 cacheKey = exp;
17499
17500 parsedExpression = cache[cacheKey];
17501
17502 if (!parsedExpression) {
17503 var lexer = new Lexer($parseOptions);
17504 var parser = new Parser(lexer, $filter, $parseOptions);
17505 parsedExpression = parser.parse(exp);
17506
17507 cache[cacheKey] = addWatchDelegate(parsedExpression);
17508 }
17509 return addInterceptor(parsedExpression, interceptorFn);
17510
17511 case 'function':
17512 return addInterceptor(exp, interceptorFn);
17513
17514 default:
17515 return addInterceptor(noop, interceptorFn);
17516 }
17517 }
17518
17519 function $$getAst(exp) {
17520 var lexer = new Lexer($parseOptions);
17521 var parser = new Parser(lexer, $filter, $parseOptions);
17522 return parser.getAst(exp).ast;
17523 }
17524
17525 function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {
17526
17527 if (newValue == null || oldValueOfValue == null) { // null/undefined
17528 return newValue === oldValueOfValue;
17529 }
17530
17531 if (typeof newValue === 'object') {
17532
17533 // attempt to convert the value to a primitive type
17534 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
17535 // be cheaply dirty-checked
17536 newValue = getValueOf(newValue);
17537
17538 if (typeof newValue === 'object' && !compareObjectIdentity) {
17539 // objects/arrays are not supported - deep-watching them would be too expensive
17540 return false;
17541 }
17542
17543 // fall-through to the primitive equality check
17544 }
17545
17546 //Primitive or NaN
17547 // eslint-disable-next-line no-self-compare
17548 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
17549 }
17550
17551 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
17552 var inputExpressions = parsedExpression.inputs;
17553 var lastResult;
17554
17555 if (inputExpressions.length === 1) {
17556 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
17557 inputExpressions = inputExpressions[0];
17558 return scope.$watch(function expressionInputWatch(scope) {
17559 var newInputValue = inputExpressions(scope);
17560 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) {
17561 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
17562 oldInputValueOf = newInputValue && getValueOf(newInputValue);
17563 }
17564 return lastResult;
17565 }, listener, objectEquality, prettyPrintExpression);
17566 }
17567
17568 var oldInputValueOfValues = [];
17569 var oldInputValues = [];
17570 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
17571 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
17572 oldInputValues[i] = null;
17573 }
17574
17575 return scope.$watch(function expressionInputsWatch(scope) {
17576 var changed = false;
17577
17578 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
17579 var newInputValue = inputExpressions[i](scope);
17580 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) {
17581 oldInputValues[i] = newInputValue;
17582 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
17583 }
17584 }
17585
17586 if (changed) {
17587 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
17588 }
17589
17590 return lastResult;
17591 }, listener, objectEquality, prettyPrintExpression);
17592 }
17593
17594 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
17595 var isDone = parsedExpression.literal ? isAllDefined : isDefined;
17596 var unwatch, lastValue;
17597
17598 var exp = parsedExpression.$$intercepted || parsedExpression;
17599 var post = parsedExpression.$$interceptor || identity;
17600
17601 var useInputs = parsedExpression.inputs && !exp.inputs;
17602
17603 // Propagate the literal/inputs/constant attributes
17604 // ... but not oneTime since we are handling it
17605 oneTimeWatch.literal = parsedExpression.literal;
17606 oneTimeWatch.constant = parsedExpression.constant;
17607 oneTimeWatch.inputs = parsedExpression.inputs;
17608
17609 // Allow other delegates to run on this wrapped expression
17610 addWatchDelegate(oneTimeWatch);
17611
17612 unwatch = scope.$watch(oneTimeWatch, listener, objectEquality, prettyPrintExpression);
17613
17614 return unwatch;
17615
17616 function unwatchIfDone() {
17617 if (isDone(lastValue)) {
17618 unwatch();
17619 }
17620 }
17621
17622 function oneTimeWatch(scope, locals, assign, inputs) {
17623 lastValue = useInputs && inputs ? inputs[0] : exp(scope, locals, assign, inputs);
17624 if (isDone(lastValue)) {
17625 scope.$$postDigest(unwatchIfDone);
17626 }
17627 return post(lastValue);
17628 }
17629 }
17630
17631 function isAllDefined(value) {
17632 var allDefined = true;
17633 forEach(value, function(val) {
17634 if (!isDefined(val)) allDefined = false;
17635 });
17636 return allDefined;
17637 }
17638
17639 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
17640 var unwatch = scope.$watch(function constantWatch(scope) {
17641 unwatch();
17642 return parsedExpression(scope);
17643 }, listener, objectEquality);
17644 return unwatch;
17645 }
17646
17647 function addWatchDelegate(parsedExpression) {
17648 if (parsedExpression.constant) {
17649 parsedExpression.$$watchDelegate = constantWatchDelegate;
17650 } else if (parsedExpression.oneTime) {
17651 parsedExpression.$$watchDelegate = oneTimeWatchDelegate;
17652 } else if (parsedExpression.inputs) {
17653 parsedExpression.$$watchDelegate = inputsWatchDelegate;
17654 }
17655
17656 return parsedExpression;
17657 }
17658
17659 function chainInterceptors(first, second) {
17660 function chainedInterceptor(value) {
17661 return second(first(value));
17662 }
17663 chainedInterceptor.$stateful = first.$stateful || second.$stateful;
17664 chainedInterceptor.$$pure = first.$$pure && second.$$pure;
17665
17666 return chainedInterceptor;
17667 }
17668
17669 function addInterceptor(parsedExpression, interceptorFn) {
17670 if (!interceptorFn) return parsedExpression;
17671
17672 // Extract any existing interceptors out of the parsedExpression
17673 // to ensure the original parsedExpression is always the $$intercepted
17674 if (parsedExpression.$$interceptor) {
17675 interceptorFn = chainInterceptors(parsedExpression.$$interceptor, interceptorFn);
17676 parsedExpression = parsedExpression.$$intercepted;
17677 }
17678
17679 var useInputs = false;
17680
17681 var fn = function interceptedExpression(scope, locals, assign, inputs) {
17682 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
17683 return interceptorFn(value);
17684 };
17685
17686 // Maintain references to the interceptor/intercepted
17687 fn.$$intercepted = parsedExpression;
17688 fn.$$interceptor = interceptorFn;
17689
17690 // Propagate the literal/oneTime/constant attributes
17691 fn.literal = parsedExpression.literal;
17692 fn.oneTime = parsedExpression.oneTime;
17693 fn.constant = parsedExpression.constant;
17694
17695 // Treat the interceptor like filters.
17696 // If it is not $stateful then only watch its inputs.
17697 // If the expression itself has no inputs then use the full expression as an input.
17698 if (!interceptorFn.$stateful) {
17699 useInputs = !parsedExpression.inputs;
17700 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
17701
17702 if (!interceptorFn.$$pure) {
17703 fn.inputs = fn.inputs.map(function(e) {
17704 // Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a
17705 // non-pure interceptor function.
17706 if (e.isPure === PURITY_RELATIVE) {
17707 return function depurifier(s) { return e(s); };
17708 }
17709 return e;
17710 });
17711 }
17712 }
17713
17714 return addWatchDelegate(fn);
17715 }
17716 }];
17717}
17718
17719/**
17720 * @ngdoc service
17721 * @name $q
17722 * @requires $rootScope
17723 *
17724 * @description
17725 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
17726 * when they are done processing.
17727 *
17728 * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred
17729 * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
17730 *
17731 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
17732 * implementations, and the other which resembles ES6 (ES2015) promises to some degree.
17733 *
17734 * ## $q constructor
17735 *
17736 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
17737 * function as the first argument. This is similar to the native Promise implementation from ES6,
17738 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
17739 *
17740 * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are
17741 * available yet.
17742 *
17743 * It can be used like so:
17744 *
17745 * ```js
17746 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
17747 * // are available in the current lexical scope (they could have been injected or passed in).
17748 *
17749 * function asyncGreet(name) {
17750 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
17751 * return $q(function(resolve, reject) {
17752 * setTimeout(function() {
17753 * if (okToGreet(name)) {
17754 * resolve('Hello, ' + name + '!');
17755 * } else {
17756 * reject('Greeting ' + name + ' is not allowed.');
17757 * }
17758 * }, 1000);
17759 * });
17760 * }
17761 *
17762 * var promise = asyncGreet('Robin Hood');
17763 * promise.then(function(greeting) {
17764 * alert('Success: ' + greeting);
17765 * }, function(reason) {
17766 * alert('Failed: ' + reason);
17767 * });
17768 * ```
17769 *
17770 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
17771 *
17772 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise.
17773 *
17774 * However, the more traditional CommonJS-style usage is still available, and documented below.
17775 *
17776 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
17777 * interface for interacting with an object that represents the result of an action that is
17778 * performed asynchronously, and may or may not be finished at any given point in time.
17779 *
17780 * From the perspective of dealing with error handling, deferred and promise APIs are to
17781 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
17782 *
17783 * ```js
17784 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
17785 * // are available in the current lexical scope (they could have been injected or passed in).
17786 *
17787 * function asyncGreet(name) {
17788 * var deferred = $q.defer();
17789 *
17790 * setTimeout(function() {
17791 * deferred.notify('About to greet ' + name + '.');
17792 *
17793 * if (okToGreet(name)) {
17794 * deferred.resolve('Hello, ' + name + '!');
17795 * } else {
17796 * deferred.reject('Greeting ' + name + ' is not allowed.');
17797 * }
17798 * }, 1000);
17799 *
17800 * return deferred.promise;
17801 * }
17802 *
17803 * var promise = asyncGreet('Robin Hood');
17804 * promise.then(function(greeting) {
17805 * alert('Success: ' + greeting);
17806 * }, function(reason) {
17807 * alert('Failed: ' + reason);
17808 * }, function(update) {
17809 * alert('Got notification: ' + update);
17810 * });
17811 * ```
17812 *
17813 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
17814 * comes in the way of guarantees that promise and deferred APIs make, see
17815 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
17816 *
17817 * Additionally the promise api allows for composition that is very hard to do with the
17818 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
17819 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
17820 * section on serial or parallel joining of promises.
17821 *
17822 * ## The Deferred API
17823 *
17824 * A new instance of deferred is constructed by calling `$q.defer()`.
17825 *
17826 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
17827 * that can be used for signaling the successful or unsuccessful completion, as well as the status
17828 * of the task.
17829 *
17830 * **Methods**
17831 *
17832 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
17833 * constructed via `$q.reject`, the promise will be rejected instead.
17834 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
17835 * resolving it with a rejection constructed via `$q.reject`.
17836 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
17837 * multiple times before the promise is either resolved or rejected.
17838 *
17839 * **Properties**
17840 *
17841 * - promise – `{Promise}` – promise object associated with this deferred.
17842 *
17843 *
17844 * ## The Promise API
17845 *
17846 * A new promise instance is created when a deferred instance is created and can be retrieved by
17847 * calling `deferred.promise`.
17848 *
17849 * The purpose of the promise object is to allow for interested parties to get access to the result
17850 * of the deferred task when it completes.
17851 *
17852 * **Methods**
17853 *
17854 * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or
17855 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
17856 * as soon as the result is available. The callbacks are called with a single argument: the result
17857 * or rejection reason. Additionally, the notify callback may be called zero or more times to
17858 * provide a progress indication, before the promise is resolved or rejected.
17859 *
17860 * This method *returns a new promise* which is resolved or rejected via the return value of the
17861 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
17862 * with the value which is resolved in that promise using
17863 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
17864 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
17865 * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback
17866 * arguments are optional.
17867 *
17868 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
17869 *
17870 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
17871 * but to do so without modifying the final value. This is useful to release resources or do some
17872 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
17873 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
17874 * more information.
17875 *
17876 * ## Chaining promises
17877 *
17878 * Because calling the `then` method of a promise returns a new derived promise, it is easily
17879 * possible to create a chain of promises:
17880 *
17881 * ```js
17882 * promiseB = promiseA.then(function(result) {
17883 * return result + 1;
17884 * });
17885 *
17886 * // promiseB will be resolved immediately after promiseA is resolved and its value
17887 * // will be the result of promiseA incremented by 1
17888 * ```
17889 *
17890 * It is possible to create chains of any length and since a promise can be resolved with another
17891 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
17892 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
17893 * $http's response interceptors.
17894 *
17895 *
17896 * ## Differences between Kris Kowal's Q and $q
17897 *
17898 * There are two main differences:
17899 *
17900 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
17901 * mechanism in AngularJS, which means faster propagation of resolution or rejection into your
17902 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
17903 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
17904 * all the important functionality needed for common async tasks.
17905 *
17906 * ## Testing
17907 *
17908 * ```js
17909 * it('should simulate promise', inject(function($q, $rootScope) {
17910 * var deferred = $q.defer();
17911 * var promise = deferred.promise;
17912 * var resolvedValue;
17913 *
17914 * promise.then(function(value) { resolvedValue = value; });
17915 * expect(resolvedValue).toBeUndefined();
17916 *
17917 * // Simulate resolving of promise
17918 * deferred.resolve(123);
17919 * // Note that the 'then' function does not get called synchronously.
17920 * // This is because we want the promise API to always be async, whether or not
17921 * // it got called synchronously or asynchronously.
17922 * expect(resolvedValue).toBeUndefined();
17923 *
17924 * // Propagate promise resolution to 'then' functions using $apply().
17925 * $rootScope.$apply();
17926 * expect(resolvedValue).toEqual(123);
17927 * }));
17928 * ```
17929 *
17930 * @param {function(function, function)} resolver Function which is responsible for resolving or
17931 * rejecting the newly created promise. The first parameter is a function which resolves the
17932 * promise, the second parameter is a function which rejects the promise.
17933 *
17934 * @returns {Promise} The newly created promise.
17935 */
17936/**
17937 * @ngdoc provider
17938 * @name $qProvider
17939 * @this
17940 *
17941 * @description
17942 */
17943function $QProvider() {
17944 var errorOnUnhandledRejections = true;
17945 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
17946 return qFactory(function(callback) {
17947 $rootScope.$evalAsync(callback);
17948 }, $exceptionHandler, errorOnUnhandledRejections);
17949 }];
17950
17951 /**
17952 * @ngdoc method
17953 * @name $qProvider#errorOnUnhandledRejections
17954 * @kind function
17955 *
17956 * @description
17957 * Retrieves or overrides whether to generate an error when a rejected promise is not handled.
17958 * This feature is enabled by default.
17959 *
17960 * @param {boolean=} value Whether to generate an error when a rejected promise is not handled.
17961 * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for
17962 * chaining otherwise.
17963 */
17964 this.errorOnUnhandledRejections = function(value) {
17965 if (isDefined(value)) {
17966 errorOnUnhandledRejections = value;
17967 return this;
17968 } else {
17969 return errorOnUnhandledRejections;
17970 }
17971 };
17972}
17973
17974/** @this */
17975function $$QProvider() {
17976 var errorOnUnhandledRejections = true;
17977 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
17978 return qFactory(function(callback) {
17979 $browser.defer(callback);
17980 }, $exceptionHandler, errorOnUnhandledRejections);
17981 }];
17982
17983 this.errorOnUnhandledRejections = function(value) {
17984 if (isDefined(value)) {
17985 errorOnUnhandledRejections = value;
17986 return this;
17987 } else {
17988 return errorOnUnhandledRejections;
17989 }
17990 };
17991}
17992
17993/**
17994 * Constructs a promise manager.
17995 *
17996 * @param {function(function)} nextTick Function for executing functions in the next turn.
17997 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
17998 * debugging purposes.
17999 * @param {boolean=} errorOnUnhandledRejections Whether an error should be generated on unhandled
18000 * promises rejections.
18001 * @returns {object} Promise manager.
18002 */
18003function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
18004 var $qMinErr = minErr('$q', TypeError);
18005 var queueSize = 0;
18006 var checkQueue = [];
18007
18008 /**
18009 * @ngdoc method
18010 * @name ng.$q#defer
18011 * @kind function
18012 *
18013 * @description
18014 * Creates a `Deferred` object which represents a task which will finish in the future.
18015 *
18016 * @returns {Deferred} Returns a new instance of deferred.
18017 */
18018 function defer() {
18019 return new Deferred();
18020 }
18021
18022 function Deferred() {
18023 var promise = this.promise = new Promise();
18024 //Non prototype methods necessary to support unbound execution :/
18025 this.resolve = function(val) { resolvePromise(promise, val); };
18026 this.reject = function(reason) { rejectPromise(promise, reason); };
18027 this.notify = function(progress) { notifyPromise(promise, progress); };
18028 }
18029
18030
18031 function Promise() {
18032 this.$$state = { status: 0 };
18033 }
18034
18035 extend(Promise.prototype, {
18036 then: function(onFulfilled, onRejected, progressBack) {
18037 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
18038 return this;
18039 }
18040 var result = new Promise();
18041
18042 this.$$state.pending = this.$$state.pending || [];
18043 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
18044 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
18045
18046 return result;
18047 },
18048
18049 'catch': function(callback) {
18050 return this.then(null, callback);
18051 },
18052
18053 'finally': function(callback, progressBack) {
18054 return this.then(function(value) {
18055 return handleCallback(value, resolve, callback);
18056 }, function(error) {
18057 return handleCallback(error, reject, callback);
18058 }, progressBack);
18059 }
18060 });
18061
18062 function processQueue(state) {
18063 var fn, promise, pending;
18064
18065 pending = state.pending;
18066 state.processScheduled = false;
18067 state.pending = undefined;
18068 try {
18069 for (var i = 0, ii = pending.length; i < ii; ++i) {
18070 markQStateExceptionHandled(state);
18071 promise = pending[i][0];
18072 fn = pending[i][state.status];
18073 try {
18074 if (isFunction(fn)) {
18075 resolvePromise(promise, fn(state.value));
18076 } else if (state.status === 1) {
18077 resolvePromise(promise, state.value);
18078 } else {
18079 rejectPromise(promise, state.value);
18080 }
18081 } catch (e) {
18082 rejectPromise(promise, e);
18083 // This error is explicitly marked for being passed to the $exceptionHandler
18084 if (e && e.$$passToExceptionHandler === true) {
18085 exceptionHandler(e);
18086 }
18087 }
18088 }
18089 } finally {
18090 --queueSize;
18091 if (errorOnUnhandledRejections && queueSize === 0) {
18092 nextTick(processChecks);
18093 }
18094 }
18095 }
18096
18097 function processChecks() {
18098 // eslint-disable-next-line no-unmodified-loop-condition
18099 while (!queueSize && checkQueue.length) {
18100 var toCheck = checkQueue.shift();
18101 if (!isStateExceptionHandled(toCheck)) {
18102 markQStateExceptionHandled(toCheck);
18103 var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value);
18104 if (isError(toCheck.value)) {
18105 exceptionHandler(toCheck.value, errorMessage);
18106 } else {
18107 exceptionHandler(errorMessage);
18108 }
18109 }
18110 }
18111 }
18112
18113 function scheduleProcessQueue(state) {
18114 if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !isStateExceptionHandled(state)) {
18115 if (queueSize === 0 && checkQueue.length === 0) {
18116 nextTick(processChecks);
18117 }
18118 checkQueue.push(state);
18119 }
18120 if (state.processScheduled || !state.pending) return;
18121 state.processScheduled = true;
18122 ++queueSize;
18123 nextTick(function() { processQueue(state); });
18124 }
18125
18126 function resolvePromise(promise, val) {
18127 if (promise.$$state.status) return;
18128 if (val === promise) {
18129 $$reject(promise, $qMinErr(
18130 'qcycle',
18131 'Expected promise to be resolved with value other than itself \'{0}\'',
18132 val));
18133 } else {
18134 $$resolve(promise, val);
18135 }
18136
18137 }
18138
18139 function $$resolve(promise, val) {
18140 var then;
18141 var done = false;
18142 try {
18143 if (isObject(val) || isFunction(val)) then = val.then;
18144 if (isFunction(then)) {
18145 promise.$$state.status = -1;
18146 then.call(val, doResolve, doReject, doNotify);
18147 } else {
18148 promise.$$state.value = val;
18149 promise.$$state.status = 1;
18150 scheduleProcessQueue(promise.$$state);
18151 }
18152 } catch (e) {
18153 doReject(e);
18154 }
18155
18156 function doResolve(val) {
18157 if (done) return;
18158 done = true;
18159 $$resolve(promise, val);
18160 }
18161 function doReject(val) {
18162 if (done) return;
18163 done = true;
18164 $$reject(promise, val);
18165 }
18166 function doNotify(progress) {
18167 notifyPromise(promise, progress);
18168 }
18169 }
18170
18171 function rejectPromise(promise, reason) {
18172 if (promise.$$state.status) return;
18173 $$reject(promise, reason);
18174 }
18175
18176 function $$reject(promise, reason) {
18177 promise.$$state.value = reason;
18178 promise.$$state.status = 2;
18179 scheduleProcessQueue(promise.$$state);
18180 }
18181
18182 function notifyPromise(promise, progress) {
18183 var callbacks = promise.$$state.pending;
18184
18185 if ((promise.$$state.status <= 0) && callbacks && callbacks.length) {
18186 nextTick(function() {
18187 var callback, result;
18188 for (var i = 0, ii = callbacks.length; i < ii; i++) {
18189 result = callbacks[i][0];
18190 callback = callbacks[i][3];
18191 try {
18192 notifyPromise(result, isFunction(callback) ? callback(progress) : progress);
18193 } catch (e) {
18194 exceptionHandler(e);
18195 }
18196 }
18197 });
18198 }
18199 }
18200
18201 /**
18202 * @ngdoc method
18203 * @name $q#reject
18204 * @kind function
18205 *
18206 * @description
18207 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
18208 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
18209 * a promise chain, you don't need to worry about it.
18210 *
18211 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
18212 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
18213 * a promise error callback and you want to forward the error to the promise derived from the
18214 * current promise, you have to "rethrow" the error by returning a rejection constructed via
18215 * `reject`.
18216 *
18217 * ```js
18218 * promiseB = promiseA.then(function(result) {
18219 * // success: do something and resolve promiseB
18220 * // with the old or a new result
18221 * return result;
18222 * }, function(reason) {
18223 * // error: handle the error if possible and
18224 * // resolve promiseB with newPromiseOrValue,
18225 * // otherwise forward the rejection to promiseB
18226 * if (canHandle(reason)) {
18227 * // handle the error and recover
18228 * return newPromiseOrValue;
18229 * }
18230 * return $q.reject(reason);
18231 * });
18232 * ```
18233 *
18234 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
18235 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
18236 */
18237 function reject(reason) {
18238 var result = new Promise();
18239 rejectPromise(result, reason);
18240 return result;
18241 }
18242
18243 function handleCallback(value, resolver, callback) {
18244 var callbackOutput = null;
18245 try {
18246 if (isFunction(callback)) callbackOutput = callback();
18247 } catch (e) {
18248 return reject(e);
18249 }
18250 if (isPromiseLike(callbackOutput)) {
18251 return callbackOutput.then(function() {
18252 return resolver(value);
18253 }, reject);
18254 } else {
18255 return resolver(value);
18256 }
18257 }
18258
18259 /**
18260 * @ngdoc method
18261 * @name $q#when
18262 * @kind function
18263 *
18264 * @description
18265 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
18266 * This is useful when you are dealing with an object that might or might not be a promise, or if
18267 * the promise comes from a source that can't be trusted.
18268 *
18269 * @param {*} value Value or a promise
18270 * @param {Function=} successCallback
18271 * @param {Function=} errorCallback
18272 * @param {Function=} progressCallback
18273 * @returns {Promise} Returns a promise of the passed value or promise
18274 */
18275
18276
18277 function when(value, callback, errback, progressBack) {
18278 var result = new Promise();
18279 resolvePromise(result, value);
18280 return result.then(callback, errback, progressBack);
18281 }
18282
18283 /**
18284 * @ngdoc method
18285 * @name $q#resolve
18286 * @kind function
18287 *
18288 * @description
18289 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
18290 *
18291 * @param {*} value Value or a promise
18292 * @param {Function=} successCallback
18293 * @param {Function=} errorCallback
18294 * @param {Function=} progressCallback
18295 * @returns {Promise} Returns a promise of the passed value or promise
18296 */
18297 var resolve = when;
18298
18299 /**
18300 * @ngdoc method
18301 * @name $q#all
18302 * @kind function
18303 *
18304 * @description
18305 * Combines multiple promises into a single promise that is resolved when all of the input
18306 * promises are resolved.
18307 *
18308 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
18309 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
18310 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
18311 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
18312 * with the same rejection value.
18313 */
18314
18315 function all(promises) {
18316 var result = new Promise(),
18317 counter = 0,
18318 results = isArray(promises) ? [] : {};
18319
18320 forEach(promises, function(promise, key) {
18321 counter++;
18322 when(promise).then(function(value) {
18323 results[key] = value;
18324 if (!(--counter)) resolvePromise(result, results);
18325 }, function(reason) {
18326 rejectPromise(result, reason);
18327 });
18328 });
18329
18330 if (counter === 0) {
18331 resolvePromise(result, results);
18332 }
18333
18334 return result;
18335 }
18336
18337 /**
18338 * @ngdoc method
18339 * @name $q#race
18340 * @kind function
18341 *
18342 * @description
18343 * Returns a promise that resolves or rejects as soon as one of those promises
18344 * resolves or rejects, with the value or reason from that promise.
18345 *
18346 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
18347 * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises`
18348 * resolves or rejects, with the value or reason from that promise.
18349 */
18350
18351 function race(promises) {
18352 var deferred = defer();
18353
18354 forEach(promises, function(promise) {
18355 when(promise).then(deferred.resolve, deferred.reject);
18356 });
18357
18358 return deferred.promise;
18359 }
18360
18361 function $Q(resolver) {
18362 if (!isFunction(resolver)) {
18363 throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver);
18364 }
18365
18366 var promise = new Promise();
18367
18368 function resolveFn(value) {
18369 resolvePromise(promise, value);
18370 }
18371
18372 function rejectFn(reason) {
18373 rejectPromise(promise, reason);
18374 }
18375
18376 resolver(resolveFn, rejectFn);
18377
18378 return promise;
18379 }
18380
18381 // Let's make the instanceof operator work for promises, so that
18382 // `new $q(fn) instanceof $q` would evaluate to true.
18383 $Q.prototype = Promise.prototype;
18384
18385 $Q.defer = defer;
18386 $Q.reject = reject;
18387 $Q.when = when;
18388 $Q.resolve = resolve;
18389 $Q.all = all;
18390 $Q.race = race;
18391
18392 return $Q;
18393}
18394
18395function isStateExceptionHandled(state) {
18396 return !!state.pur;
18397}
18398function markQStateExceptionHandled(state) {
18399 state.pur = true;
18400}
18401function markQExceptionHandled(q) {
18402 // Built-in `$q` promises will always have a `$$state` property. This check is to allow
18403 // overwriting `$q` with a different promise library (e.g. Bluebird + angular-bluebird-promises).
18404 // (Currently, this is the only method that might be called with a promise, even if it is not
18405 // created by the built-in `$q`.)
18406 if (q.$$state) {
18407 markQStateExceptionHandled(q.$$state);
18408 }
18409}
18410
18411/** @this */
18412function $$RAFProvider() { //rAF
18413 this.$get = ['$window', '$timeout', function($window, $timeout) {
18414 var requestAnimationFrame = $window.requestAnimationFrame ||
18415 $window.webkitRequestAnimationFrame;
18416
18417 var cancelAnimationFrame = $window.cancelAnimationFrame ||
18418 $window.webkitCancelAnimationFrame ||
18419 $window.webkitCancelRequestAnimationFrame;
18420
18421 var rafSupported = !!requestAnimationFrame;
18422 var raf = rafSupported
18423 ? function(fn) {
18424 var id = requestAnimationFrame(fn);
18425 return function() {
18426 cancelAnimationFrame(id);
18427 };
18428 }
18429 : function(fn) {
18430 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
18431 return function() {
18432 $timeout.cancel(timer);
18433 };
18434 };
18435
18436 raf.supported = rafSupported;
18437
18438 return raf;
18439 }];
18440}
18441
18442/**
18443 * DESIGN NOTES
18444 *
18445 * The design decisions behind the scope are heavily favored for speed and memory consumption.
18446 *
18447 * The typical use of scope is to watch the expressions, which most of the time return the same
18448 * value as last time so we optimize the operation.
18449 *
18450 * Closures construction is expensive in terms of speed as well as memory:
18451 * - No closures, instead use prototypical inheritance for API
18452 * - Internal state needs to be stored on scope directly, which means that private state is
18453 * exposed as $$____ properties
18454 *
18455 * Loop operations are optimized by using while(count--) { ... }
18456 * - This means that in order to keep the same order of execution as addition we have to add
18457 * items to the array at the beginning (unshift) instead of at the end (push)
18458 *
18459 * Child scopes are created and removed often
18460 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
18461 *
18462 * There are fewer watches than observers. This is why you don't want the observer to be implemented
18463 * in the same way as watch. Watch requires return of the initialization function which is expensive
18464 * to construct.
18465 */
18466
18467
18468/**
18469 * @ngdoc provider
18470 * @name $rootScopeProvider
18471 * @description
18472 *
18473 * Provider for the $rootScope service.
18474 */
18475
18476/**
18477 * @ngdoc method
18478 * @name $rootScopeProvider#digestTtl
18479 * @description
18480 *
18481 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
18482 * assuming that the model is unstable.
18483 *
18484 * The current default is 10 iterations.
18485 *
18486 * In complex applications it's possible that the dependencies between `$watch`s will result in
18487 * several digest iterations. However if an application needs more than the default 10 digest
18488 * iterations for its model to stabilize then you should investigate what is causing the model to
18489 * continuously change during the digest.
18490 *
18491 * Increasing the TTL could have performance implications, so you should not change it without
18492 * proper justification.
18493 *
18494 * @param {number} limit The number of digest iterations.
18495 */
18496
18497
18498/**
18499 * @ngdoc service
18500 * @name $rootScope
18501 * @this
18502 *
18503 * @description
18504 *
18505 * Every application has a single root {@link ng.$rootScope.Scope scope}.
18506 * All other scopes are descendant scopes of the root scope. Scopes provide separation
18507 * between the model and the view, via a mechanism for watching the model for changes.
18508 * They also provide event emission/broadcast and subscription facility. See the
18509 * {@link guide/scope developer guide on scopes}.
18510 */
18511function $RootScopeProvider() {
18512 var TTL = 10;
18513 var $rootScopeMinErr = minErr('$rootScope');
18514 var lastDirtyWatch = null;
18515 var applyAsyncId = null;
18516
18517 this.digestTtl = function(value) {
18518 if (arguments.length) {
18519 TTL = value;
18520 }
18521 return TTL;
18522 };
18523
18524 function createChildScopeClass(parent) {
18525 function ChildScope() {
18526 this.$$watchers = this.$$nextSibling =
18527 this.$$childHead = this.$$childTail = null;
18528 this.$$listeners = {};
18529 this.$$listenerCount = {};
18530 this.$$watchersCount = 0;
18531 this.$id = nextUid();
18532 this.$$ChildScope = null;
18533 this.$$suspended = false;
18534 }
18535 ChildScope.prototype = parent;
18536 return ChildScope;
18537 }
18538
18539 this.$get = ['$exceptionHandler', '$parse', '$browser',
18540 function($exceptionHandler, $parse, $browser) {
18541
18542 function destroyChildScope($event) {
18543 $event.currentScope.$$destroyed = true;
18544 }
18545
18546 function cleanUpScope($scope) {
18547
18548 // Support: IE 9 only
18549 if (msie === 9) {
18550 // There is a memory leak in IE9 if all child scopes are not disconnected
18551 // completely when a scope is destroyed. So this code will recurse up through
18552 // all this scopes children
18553 //
18554 // See issue https://github.com/angular/angular.js/issues/10706
18555 if ($scope.$$childHead) {
18556 cleanUpScope($scope.$$childHead);
18557 }
18558 if ($scope.$$nextSibling) {
18559 cleanUpScope($scope.$$nextSibling);
18560 }
18561 }
18562
18563 // The code below works around IE9 and V8's memory leaks
18564 //
18565 // See:
18566 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
18567 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
18568 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
18569
18570 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
18571 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
18572 }
18573
18574 /**
18575 * @ngdoc type
18576 * @name $rootScope.Scope
18577 *
18578 * @description
18579 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
18580 * {@link auto.$injector $injector}. Child scopes are created using the
18581 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
18582 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
18583 * an in-depth introduction and usage examples.
18584 *
18585 *
18586 * ## Inheritance
18587 * A scope can inherit from a parent scope, as in this example:
18588 * ```js
18589 var parent = $rootScope;
18590 var child = parent.$new();
18591
18592 parent.salutation = "Hello";
18593 expect(child.salutation).toEqual('Hello');
18594
18595 child.salutation = "Welcome";
18596 expect(child.salutation).toEqual('Welcome');
18597 expect(parent.salutation).toEqual('Hello');
18598 * ```
18599 *
18600 * When interacting with `Scope` in tests, additional helper methods are available on the
18601 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
18602 * details.
18603 *
18604 *
18605 * @param {Object.<string, function()>=} providers Map of service factory which need to be
18606 * provided for the current scope. Defaults to {@link ng}.
18607 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
18608 * append/override services provided by `providers`. This is handy
18609 * when unit-testing and having the need to override a default
18610 * service.
18611 * @returns {Object} Newly created scope.
18612 *
18613 */
18614 function Scope() {
18615 this.$id = nextUid();
18616 this.$$phase = this.$parent = this.$$watchers =
18617 this.$$nextSibling = this.$$prevSibling =
18618 this.$$childHead = this.$$childTail = null;
18619 this.$root = this;
18620 this.$$destroyed = false;
18621 this.$$suspended = false;
18622 this.$$listeners = {};
18623 this.$$listenerCount = {};
18624 this.$$watchersCount = 0;
18625 this.$$isolateBindings = null;
18626 }
18627
18628 /**
18629 * @ngdoc property
18630 * @name $rootScope.Scope#$id
18631 *
18632 * @description
18633 * Unique scope ID (monotonically increasing) useful for debugging.
18634 */
18635
18636 /**
18637 * @ngdoc property
18638 * @name $rootScope.Scope#$parent
18639 *
18640 * @description
18641 * Reference to the parent scope.
18642 */
18643
18644 /**
18645 * @ngdoc property
18646 * @name $rootScope.Scope#$root
18647 *
18648 * @description
18649 * Reference to the root scope.
18650 */
18651
18652 Scope.prototype = {
18653 constructor: Scope,
18654 /**
18655 * @ngdoc method
18656 * @name $rootScope.Scope#$new
18657 * @kind function
18658 *
18659 * @description
18660 * Creates a new child {@link ng.$rootScope.Scope scope}.
18661 *
18662 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
18663 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
18664 *
18665 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
18666 * desired for the scope and its child scopes to be permanently detached from the parent and
18667 * thus stop participating in model change detection and listener notification by invoking.
18668 *
18669 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
18670 * parent scope. The scope is isolated, as it can not see parent scope properties.
18671 * When creating widgets, it is useful for the widget to not accidentally read parent
18672 * state.
18673 *
18674 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
18675 * of the newly created scope. Defaults to `this` scope if not provided.
18676 * This is used when creating a transclude scope to correctly place it
18677 * in the scope hierarchy while maintaining the correct prototypical
18678 * inheritance.
18679 *
18680 * @returns {Object} The newly created child scope.
18681 *
18682 */
18683 $new: function(isolate, parent) {
18684 var child;
18685
18686 parent = parent || this;
18687
18688 if (isolate) {
18689 child = new Scope();
18690 child.$root = this.$root;
18691 } else {
18692 // Only create a child scope class if somebody asks for one,
18693 // but cache it to allow the VM to optimize lookups.
18694 if (!this.$$ChildScope) {
18695 this.$$ChildScope = createChildScopeClass(this);
18696 }
18697 child = new this.$$ChildScope();
18698 }
18699 child.$parent = parent;
18700 child.$$prevSibling = parent.$$childTail;
18701 if (parent.$$childHead) {
18702 parent.$$childTail.$$nextSibling = child;
18703 parent.$$childTail = child;
18704 } else {
18705 parent.$$childHead = parent.$$childTail = child;
18706 }
18707
18708 // When the new scope is not isolated or we inherit from `this`, and
18709 // the parent scope is destroyed, the property `$$destroyed` is inherited
18710 // prototypically. In all other cases, this property needs to be set
18711 // when the parent scope is destroyed.
18712 // The listener needs to be added after the parent is set
18713 if (isolate || parent !== this) child.$on('$destroy', destroyChildScope);
18714
18715 return child;
18716 },
18717
18718 /**
18719 * @ngdoc method
18720 * @name $rootScope.Scope#$watch
18721 * @kind function
18722 *
18723 * @description
18724 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
18725 *
18726 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
18727 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
18728 * its value when executed multiple times with the same input because it may be executed multiple
18729 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
18730 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).)
18731 * - The `listener` is called only when the value from the current `watchExpression` and the
18732 * previous call to `watchExpression` are not equal (with the exception of the initial run,
18733 * see below). Inequality is determined according to reference inequality,
18734 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
18735 * via the `!==` Javascript operator, unless `objectEquality == true`
18736 * (see next point)
18737 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
18738 * according to the {@link angular.equals} function. To save the value of the object for
18739 * later comparison, the {@link angular.copy} function is used. This therefore means that
18740 * watching complex objects will have adverse memory and performance implications.
18741 * - This should not be used to watch for changes in objects that are (or contain)
18742 * [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}.
18743 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
18744 * This is achieved by rerunning the watchers until no changes are detected. The rerun
18745 * iteration limit is 10 to prevent an infinite loop deadlock.
18746 *
18747 *
18748 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
18749 * you can register a `watchExpression` function with no `listener`. (Be prepared for
18750 * multiple calls to your `watchExpression` because it will execute multiple times in a
18751 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
18752 *
18753 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
18754 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
18755 * watcher. In rare cases, this is undesirable because the listener is called when the result
18756 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
18757 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
18758 * listener was called due to initialization.
18759 *
18760 *
18761 *
18762 * @example
18763 * ```js
18764 // let's assume that scope was dependency injected as the $rootScope
18765 var scope = $rootScope;
18766 scope.name = 'misko';
18767 scope.counter = 0;
18768
18769 expect(scope.counter).toEqual(0);
18770 scope.$watch('name', function(newValue, oldValue) {
18771 scope.counter = scope.counter + 1;
18772 });
18773 expect(scope.counter).toEqual(0);
18774
18775 scope.$digest();
18776 // the listener is always called during the first $digest loop after it was registered
18777 expect(scope.counter).toEqual(1);
18778
18779 scope.$digest();
18780 // but now it will not be called unless the value changes
18781 expect(scope.counter).toEqual(1);
18782
18783 scope.name = 'adam';
18784 scope.$digest();
18785 expect(scope.counter).toEqual(2);
18786
18787
18788
18789 // Using a function as a watchExpression
18790 var food;
18791 scope.foodCounter = 0;
18792 expect(scope.foodCounter).toEqual(0);
18793 scope.$watch(
18794 // This function returns the value being watched. It is called for each turn of the $digest loop
18795 function() { return food; },
18796 // This is the change listener, called when the value returned from the above function changes
18797 function(newValue, oldValue) {
18798 if ( newValue !== oldValue ) {
18799 // Only increment the counter if the value changed
18800 scope.foodCounter = scope.foodCounter + 1;
18801 }
18802 }
18803 );
18804 // No digest has been run so the counter will be zero
18805 expect(scope.foodCounter).toEqual(0);
18806
18807 // Run the digest but since food has not changed count will still be zero
18808 scope.$digest();
18809 expect(scope.foodCounter).toEqual(0);
18810
18811 // Update food and run digest. Now the counter will increment
18812 food = 'cheeseburger';
18813 scope.$digest();
18814 expect(scope.foodCounter).toEqual(1);
18815
18816 * ```
18817 *
18818 *
18819 *
18820 * @param {(function()|string)} watchExpression Expression that is evaluated on each
18821 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
18822 * a call to the `listener`.
18823 *
18824 * - `string`: Evaluated as {@link guide/expression expression}
18825 * - `function(scope)`: called with current `scope` as a parameter.
18826 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
18827 * of `watchExpression` changes.
18828 *
18829 * - `newVal` contains the current value of the `watchExpression`
18830 * - `oldVal` contains the previous value of the `watchExpression`
18831 * - `scope` refers to the current scope
18832 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of
18833 * comparing for reference equality.
18834 * @returns {function()} Returns a deregistration function for this listener.
18835 */
18836 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
18837 var get = $parse(watchExp);
18838 var fn = isFunction(listener) ? listener : noop;
18839
18840 if (get.$$watchDelegate) {
18841 return get.$$watchDelegate(this, fn, objectEquality, get, watchExp);
18842 }
18843 var scope = this,
18844 array = scope.$$watchers,
18845 watcher = {
18846 fn: fn,
18847 last: initWatchVal,
18848 get: get,
18849 exp: prettyPrintExpression || watchExp,
18850 eq: !!objectEquality
18851 };
18852
18853 lastDirtyWatch = null;
18854
18855 if (!array) {
18856 array = scope.$$watchers = [];
18857 array.$$digestWatchIndex = -1;
18858 }
18859 // we use unshift since we use a while loop in $digest for speed.
18860 // the while loop reads in reverse order.
18861 array.unshift(watcher);
18862 array.$$digestWatchIndex++;
18863 incrementWatchersCount(this, 1);
18864
18865 return function deregisterWatch() {
18866 var index = arrayRemove(array, watcher);
18867 if (index >= 0) {
18868 incrementWatchersCount(scope, -1);
18869 if (index < array.$$digestWatchIndex) {
18870 array.$$digestWatchIndex--;
18871 }
18872 }
18873 lastDirtyWatch = null;
18874 };
18875 },
18876
18877 /**
18878 * @ngdoc method
18879 * @name $rootScope.Scope#$watchGroup
18880 * @kind function
18881 *
18882 * @description
18883 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
18884 * If any one expression in the collection changes the `listener` is executed.
18885 *
18886 * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return
18887 * values are examined for changes on every call to `$digest`.
18888 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
18889 *
18890 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
18891 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
18892 *
18893 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
18894 * expression in `watchExpressions` changes
18895 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
18896 * those of `watchExpression`
18897 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
18898 * those of `watchExpression`
18899 * The `scope` refers to the current scope.
18900 * @returns {function()} Returns a de-registration function for all listeners.
18901 */
18902 $watchGroup: function(watchExpressions, listener) {
18903 var oldValues = new Array(watchExpressions.length);
18904 var newValues = new Array(watchExpressions.length);
18905 var deregisterFns = [];
18906 var self = this;
18907 var changeReactionScheduled = false;
18908 var firstRun = true;
18909
18910 if (!watchExpressions.length) {
18911 // No expressions means we call the listener ASAP
18912 var shouldCall = true;
18913 self.$evalAsync(function() {
18914 if (shouldCall) listener(newValues, newValues, self);
18915 });
18916 return function deregisterWatchGroup() {
18917 shouldCall = false;
18918 };
18919 }
18920
18921 if (watchExpressions.length === 1) {
18922 // Special case size of one
18923 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
18924 newValues[0] = value;
18925 oldValues[0] = oldValue;
18926 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
18927 });
18928 }
18929
18930 forEach(watchExpressions, function(expr, i) {
18931 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value) {
18932 newValues[i] = value;
18933 if (!changeReactionScheduled) {
18934 changeReactionScheduled = true;
18935 self.$evalAsync(watchGroupAction);
18936 }
18937 });
18938 deregisterFns.push(unwatchFn);
18939 });
18940
18941 function watchGroupAction() {
18942 changeReactionScheduled = false;
18943
18944 try {
18945 if (firstRun) {
18946 firstRun = false;
18947 listener(newValues, newValues, self);
18948 } else {
18949 listener(newValues, oldValues, self);
18950 }
18951 } finally {
18952 for (var i = 0; i < watchExpressions.length; i++) {
18953 oldValues[i] = newValues[i];
18954 }
18955 }
18956 }
18957
18958 return function deregisterWatchGroup() {
18959 while (deregisterFns.length) {
18960 deregisterFns.shift()();
18961 }
18962 };
18963 },
18964
18965
18966 /**
18967 * @ngdoc method
18968 * @name $rootScope.Scope#$watchCollection
18969 * @kind function
18970 *
18971 * @description
18972 * Shallow watches the properties of an object and fires whenever any of the properties change
18973 * (for arrays, this implies watching the array items; for object maps, this implies watching
18974 * the properties). If a change is detected, the `listener` callback is fired.
18975 *
18976 * - The `obj` collection is observed via standard $watch operation and is examined on every
18977 * call to $digest() to see if any items have been added, removed, or moved.
18978 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
18979 * adding, removing, and moving items belonging to an object or array.
18980 *
18981 *
18982 * @example
18983 * ```js
18984 $scope.names = ['igor', 'matias', 'misko', 'james'];
18985 $scope.dataCount = 4;
18986
18987 $scope.$watchCollection('names', function(newNames, oldNames) {
18988 $scope.dataCount = newNames.length;
18989 });
18990
18991 expect($scope.dataCount).toEqual(4);
18992 $scope.$digest();
18993
18994 //still at 4 ... no changes
18995 expect($scope.dataCount).toEqual(4);
18996
18997 $scope.names.pop();
18998 $scope.$digest();
18999
19000 //now there's been a change
19001 expect($scope.dataCount).toEqual(3);
19002 * ```
19003 *
19004 *
19005 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
19006 * expression value should evaluate to an object or an array which is observed on each
19007 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
19008 * collection will trigger a call to the `listener`.
19009 *
19010 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
19011 * when a change is detected.
19012 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
19013 * - The `oldCollection` object is a copy of the former collection data.
19014 * Due to performance considerations, the`oldCollection` value is computed only if the
19015 * `listener` function declares two or more arguments.
19016 * - The `scope` argument refers to the current scope.
19017 *
19018 * @returns {function()} Returns a de-registration function for this listener. When the
19019 * de-registration function is executed, the internal watch operation is terminated.
19020 */
19021 $watchCollection: function(obj, listener) {
19022 // Mark the interceptor as
19023 // ... $$pure when literal since the instance will change when any input changes
19024 $watchCollectionInterceptor.$$pure = $parse(obj).literal;
19025 // ... $stateful when non-literal since we must read the state of the collection
19026 $watchCollectionInterceptor.$stateful = !$watchCollectionInterceptor.$$pure;
19027
19028 var self = this;
19029 // the current value, updated on each dirty-check run
19030 var newValue;
19031 // a shallow copy of the newValue from the last dirty-check run,
19032 // updated to match newValue during dirty-check run
19033 var oldValue;
19034 // a shallow copy of the newValue from when the last change happened
19035 var veryOldValue;
19036 // only track veryOldValue if the listener is asking for it
19037 var trackVeryOldValue = (listener.length > 1);
19038 var changeDetected = 0;
19039 var changeDetector = $parse(obj, $watchCollectionInterceptor);
19040 var internalArray = [];
19041 var internalObject = {};
19042 var initRun = true;
19043 var oldLength = 0;
19044
19045 function $watchCollectionInterceptor(_value) {
19046 newValue = _value;
19047 var newLength, key, bothNaN, newItem, oldItem;
19048
19049 // If the new value is undefined, then return undefined as the watch may be a one-time watch
19050 if (isUndefined(newValue)) return;
19051
19052 if (!isObject(newValue)) { // if primitive
19053 if (oldValue !== newValue) {
19054 oldValue = newValue;
19055 changeDetected++;
19056 }
19057 } else if (isArrayLike(newValue)) {
19058 if (oldValue !== internalArray) {
19059 // we are transitioning from something which was not an array into array.
19060 oldValue = internalArray;
19061 oldLength = oldValue.length = 0;
19062 changeDetected++;
19063 }
19064
19065 newLength = newValue.length;
19066
19067 if (oldLength !== newLength) {
19068 // if lengths do not match we need to trigger change notification
19069 changeDetected++;
19070 oldValue.length = oldLength = newLength;
19071 }
19072 // copy the items to oldValue and look for changes.
19073 for (var i = 0; i < newLength; i++) {
19074 oldItem = oldValue[i];
19075 newItem = newValue[i];
19076
19077 // eslint-disable-next-line no-self-compare
19078 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
19079 if (!bothNaN && (oldItem !== newItem)) {
19080 changeDetected++;
19081 oldValue[i] = newItem;
19082 }
19083 }
19084 } else {
19085 if (oldValue !== internalObject) {
19086 // we are transitioning from something which was not an object into object.
19087 oldValue = internalObject = {};
19088 oldLength = 0;
19089 changeDetected++;
19090 }
19091 // copy the items to oldValue and look for changes.
19092 newLength = 0;
19093 for (key in newValue) {
19094 if (hasOwnProperty.call(newValue, key)) {
19095 newLength++;
19096 newItem = newValue[key];
19097 oldItem = oldValue[key];
19098
19099 if (key in oldValue) {
19100 // eslint-disable-next-line no-self-compare
19101 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
19102 if (!bothNaN && (oldItem !== newItem)) {
19103 changeDetected++;
19104 oldValue[key] = newItem;
19105 }
19106 } else {
19107 oldLength++;
19108 oldValue[key] = newItem;
19109 changeDetected++;
19110 }
19111 }
19112 }
19113 if (oldLength > newLength) {
19114 // we used to have more keys, need to find them and destroy them.
19115 changeDetected++;
19116 for (key in oldValue) {
19117 if (!hasOwnProperty.call(newValue, key)) {
19118 oldLength--;
19119 delete oldValue[key];
19120 }
19121 }
19122 }
19123 }
19124 return changeDetected;
19125 }
19126
19127 function $watchCollectionAction() {
19128 if (initRun) {
19129 initRun = false;
19130 listener(newValue, newValue, self);
19131 } else {
19132 listener(newValue, veryOldValue, self);
19133 }
19134
19135 // make a copy for the next time a collection is changed
19136 if (trackVeryOldValue) {
19137 if (!isObject(newValue)) {
19138 //primitive
19139 veryOldValue = newValue;
19140 } else if (isArrayLike(newValue)) {
19141 veryOldValue = new Array(newValue.length);
19142 for (var i = 0; i < newValue.length; i++) {
19143 veryOldValue[i] = newValue[i];
19144 }
19145 } else { // if object
19146 veryOldValue = {};
19147 for (var key in newValue) {
19148 if (hasOwnProperty.call(newValue, key)) {
19149 veryOldValue[key] = newValue[key];
19150 }
19151 }
19152 }
19153 }
19154 }
19155
19156 return this.$watch(changeDetector, $watchCollectionAction);
19157 },
19158
19159 /**
19160 * @ngdoc method
19161 * @name $rootScope.Scope#$digest
19162 * @kind function
19163 *
19164 * @description
19165 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
19166 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
19167 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
19168 * until no more listeners are firing. This means that it is possible to get into an infinite
19169 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
19170 * iterations exceeds 10.
19171 *
19172 * Usually, you don't call `$digest()` directly in
19173 * {@link ng.directive:ngController controllers} or in
19174 * {@link ng.$compileProvider#directive directives}.
19175 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
19176 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
19177 *
19178 * If you want to be notified whenever `$digest()` is called,
19179 * you can register a `watchExpression` function with
19180 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
19181 *
19182 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
19183 *
19184 * @example
19185 * ```js
19186 var scope = ...;
19187 scope.name = 'misko';
19188 scope.counter = 0;
19189
19190 expect(scope.counter).toEqual(0);
19191 scope.$watch('name', function(newValue, oldValue) {
19192 scope.counter = scope.counter + 1;
19193 });
19194 expect(scope.counter).toEqual(0);
19195
19196 scope.$digest();
19197 // the listener is always called during the first $digest loop after it was registered
19198 expect(scope.counter).toEqual(1);
19199
19200 scope.$digest();
19201 // but now it will not be called unless the value changes
19202 expect(scope.counter).toEqual(1);
19203
19204 scope.name = 'adam';
19205 scope.$digest();
19206 expect(scope.counter).toEqual(2);
19207 * ```
19208 *
19209 */
19210 $digest: function() {
19211 var watch, value, last, fn, get,
19212 watchers,
19213 dirty, ttl = TTL,
19214 next, current, target = asyncQueue.length ? $rootScope : this,
19215 watchLog = [],
19216 logIdx, asyncTask;
19217
19218 beginPhase('$digest');
19219 // Check for changes to browser url that happened in sync before the call to $digest
19220 $browser.$$checkUrlChange();
19221
19222 if (this === $rootScope && applyAsyncId !== null) {
19223 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
19224 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
19225 $browser.defer.cancel(applyAsyncId);
19226 flushApplyAsync();
19227 }
19228
19229 lastDirtyWatch = null;
19230
19231 do { // "while dirty" loop
19232 dirty = false;
19233 current = target;
19234
19235 // It's safe for asyncQueuePosition to be a local variable here because this loop can't
19236 // be reentered recursively. Calling $digest from a function passed to $evalAsync would
19237 // lead to a '$digest already in progress' error.
19238 for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) {
19239 try {
19240 asyncTask = asyncQueue[asyncQueuePosition];
19241 fn = asyncTask.fn;
19242 fn(asyncTask.scope, asyncTask.locals);
19243 } catch (e) {
19244 $exceptionHandler(e);
19245 }
19246 lastDirtyWatch = null;
19247 }
19248 asyncQueue.length = 0;
19249
19250 traverseScopesLoop:
19251 do { // "traverse the scopes" loop
19252 if ((watchers = !current.$$suspended && current.$$watchers)) {
19253 // process our watches
19254 watchers.$$digestWatchIndex = watchers.length;
19255 while (watchers.$$digestWatchIndex--) {
19256 try {
19257 watch = watchers[watchers.$$digestWatchIndex];
19258 // Most common watches are on primitives, in which case we can short
19259 // circuit it with === operator, only when === fails do we use .equals
19260 if (watch) {
19261 get = watch.get;
19262 if ((value = get(current)) !== (last = watch.last) &&
19263 !(watch.eq
19264 ? equals(value, last)
19265 : (isNumberNaN(value) && isNumberNaN(last)))) {
19266 dirty = true;
19267 lastDirtyWatch = watch;
19268 watch.last = watch.eq ? copy(value, null) : value;
19269 fn = watch.fn;
19270 fn(value, ((last === initWatchVal) ? value : last), current);
19271 if (ttl < 5) {
19272 logIdx = 4 - ttl;
19273 if (!watchLog[logIdx]) watchLog[logIdx] = [];
19274 watchLog[logIdx].push({
19275 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
19276 newVal: value,
19277 oldVal: last
19278 });
19279 }
19280 } else if (watch === lastDirtyWatch) {
19281 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
19282 // have already been tested.
19283 dirty = false;
19284 break traverseScopesLoop;
19285 }
19286 }
19287 } catch (e) {
19288 $exceptionHandler(e);
19289 }
19290 }
19291 }
19292
19293 // Insanity Warning: scope depth-first traversal
19294 // yes, this code is a bit crazy, but it works and we have tests to prove it!
19295 // this piece should be kept in sync with the traversal in $broadcast
19296 // (though it differs due to having the extra check for $$suspended and does not
19297 // check $$listenerCount)
19298 if (!(next = ((!current.$$suspended && current.$$watchersCount && current.$$childHead) ||
19299 (current !== target && current.$$nextSibling)))) {
19300 while (current !== target && !(next = current.$$nextSibling)) {
19301 current = current.$parent;
19302 }
19303 }
19304 } while ((current = next));
19305
19306 // `break traverseScopesLoop;` takes us to here
19307
19308 if ((dirty || asyncQueue.length) && !(ttl--)) {
19309 clearPhase();
19310 throw $rootScopeMinErr('infdig',
19311 '{0} $digest() iterations reached. Aborting!\n' +
19312 'Watchers fired in the last 5 iterations: {1}',
19313 TTL, watchLog);
19314 }
19315
19316 } while (dirty || asyncQueue.length);
19317
19318 clearPhase();
19319
19320 // postDigestQueuePosition isn't local here because this loop can be reentered recursively.
19321 while (postDigestQueuePosition < postDigestQueue.length) {
19322 try {
19323 postDigestQueue[postDigestQueuePosition++]();
19324 } catch (e) {
19325 $exceptionHandler(e);
19326 }
19327 }
19328 postDigestQueue.length = postDigestQueuePosition = 0;
19329
19330 // Check for changes to browser url that happened during the $digest
19331 // (for which no event is fired; e.g. via `history.pushState()`)
19332 $browser.$$checkUrlChange();
19333 },
19334
19335 /**
19336 * @ngdoc method
19337 * @name $rootScope.Scope#$suspend
19338 * @kind function
19339 *
19340 * @description
19341 * Suspend watchers of this scope subtree so that they will not be invoked during digest.
19342 *
19343 * This can be used to optimize your application when you know that running those watchers
19344 * is redundant.
19345 *
19346 * **Warning**
19347 *
19348 * Suspending scopes from the digest cycle can have unwanted and difficult to debug results.
19349 * Only use this approach if you are confident that you know what you are doing and have
19350 * ample tests to ensure that bindings get updated as you expect.
19351 *
19352 * Some of the things to consider are:
19353 *
19354 * * Any external event on a directive/component will not trigger a digest while the hosting
19355 * scope is suspended - even if the event handler calls `$apply()` or `$rootScope.$digest()`.
19356 * * Transcluded content exists on a scope that inherits from outside a directive but exists
19357 * as a child of the directive's containing scope. If the containing scope is suspended the
19358 * transcluded scope will also be suspended, even if the scope from which the transcluded
19359 * scope inherits is not suspended.
19360 * * Multiple directives trying to manage the suspended status of a scope can confuse each other:
19361 * * A call to `$suspend()` on an already suspended scope is a no-op.
19362 * * A call to `$resume()` on a non-suspended scope is a no-op.
19363 * * If two directives suspend a scope, then one of them resumes the scope, the scope will no
19364 * longer be suspended. This could result in the other directive believing a scope to be
19365 * suspended when it is not.
19366 * * If a parent scope is suspended then all its descendants will be also excluded from future
19367 * digests whether or not they have been suspended themselves. Note that this also applies to
19368 * isolate child scopes.
19369 * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers
19370 * for that scope and its descendants. When digesting we only check whether the current scope is
19371 * locally suspended, rather than checking whether it has a suspended ancestor.
19372 * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be
19373 * included in future digests until all its ancestors have been resumed.
19374 * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()`
19375 * against the `$rootScope` and so will still trigger a global digest even if the promise was
19376 * initiated by a component that lives on a suspended scope.
19377 */
19378 $suspend: function() {
19379 this.$$suspended = true;
19380 },
19381
19382 /**
19383 * @ngdoc method
19384 * @name $rootScope.Scope#$isSuspended
19385 * @kind function
19386 *
19387 * @description
19388 * Call this method to determine if this scope has been explicitly suspended. It will not
19389 * tell you whether an ancestor has been suspended.
19390 * To determine if this scope will be excluded from a digest triggered at the $rootScope,
19391 * for example, you must check all its ancestors:
19392 *
19393 * ```
19394 * function isExcludedFromDigest(scope) {
19395 * while(scope) {
19396 * if (scope.$isSuspended()) return true;
19397 * scope = scope.$parent;
19398 * }
19399 * return false;
19400 * ```
19401 *
19402 * Be aware that a scope may not be included in digests if it has a suspended ancestor,
19403 * even if `$isSuspended()` returns false.
19404 *
19405 * @returns true if the current scope has been suspended.
19406 */
19407 $isSuspended: function() {
19408 return this.$$suspended;
19409 },
19410
19411 /**
19412 * @ngdoc method
19413 * @name $rootScope.Scope#$resume
19414 * @kind function
19415 *
19416 * @description
19417 * Resume watchers of this scope subtree in case it was suspended.
19418 *
19419 * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach.
19420 */
19421 $resume: function() {
19422 this.$$suspended = false;
19423 },
19424
19425 /**
19426 * @ngdoc event
19427 * @name $rootScope.Scope#$destroy
19428 * @eventType broadcast on scope being destroyed
19429 *
19430 * @description
19431 * Broadcasted when a scope and its children are being destroyed.
19432 *
19433 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
19434 * clean up DOM bindings before an element is removed from the DOM.
19435 */
19436
19437 /**
19438 * @ngdoc method
19439 * @name $rootScope.Scope#$destroy
19440 * @kind function
19441 *
19442 * @description
19443 * Removes the current scope (and all of its children) from the parent scope. Removal implies
19444 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
19445 * propagate to the current scope and its children. Removal also implies that the current
19446 * scope is eligible for garbage collection.
19447 *
19448 * The `$destroy()` is usually used by directives such as
19449 * {@link ng.directive:ngRepeat ngRepeat} for managing the
19450 * unrolling of the loop.
19451 *
19452 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
19453 * Application code can register a `$destroy` event handler that will give it a chance to
19454 * perform any necessary cleanup.
19455 *
19456 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
19457 * clean up DOM bindings before an element is removed from the DOM.
19458 */
19459 $destroy: function() {
19460 // We can't destroy a scope that has been already destroyed.
19461 if (this.$$destroyed) return;
19462 var parent = this.$parent;
19463
19464 this.$broadcast('$destroy');
19465 this.$$destroyed = true;
19466
19467 if (this === $rootScope) {
19468 //Remove handlers attached to window when $rootScope is removed
19469 $browser.$$applicationDestroyed();
19470 }
19471
19472 incrementWatchersCount(this, -this.$$watchersCount);
19473 for (var eventName in this.$$listenerCount) {
19474 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
19475 }
19476
19477 // sever all the references to parent scopes (after this cleanup, the current scope should
19478 // not be retained by any of our references and should be eligible for garbage collection)
19479 if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling;
19480 if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling;
19481 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
19482 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
19483
19484 // Disable listeners, watchers and apply/digest methods
19485 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
19486 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
19487 this.$$listeners = {};
19488
19489 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
19490 this.$$nextSibling = null;
19491 cleanUpScope(this);
19492 },
19493
19494 /**
19495 * @ngdoc method
19496 * @name $rootScope.Scope#$eval
19497 * @kind function
19498 *
19499 * @description
19500 * Executes the `expression` on the current scope and returns the result. Any exceptions in
19501 * the expression are propagated (uncaught). This is useful when evaluating AngularJS
19502 * expressions.
19503 *
19504 * @example
19505 * ```js
19506 var scope = ng.$rootScope.Scope();
19507 scope.a = 1;
19508 scope.b = 2;
19509
19510 expect(scope.$eval('a+b')).toEqual(3);
19511 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
19512 * ```
19513 *
19514 * @param {(string|function())=} expression An AngularJS expression to be executed.
19515 *
19516 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
19517 * - `function(scope)`: execute the function with the current `scope` parameter.
19518 *
19519 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
19520 * @returns {*} The result of evaluating the expression.
19521 */
19522 $eval: function(expr, locals) {
19523 return $parse(expr)(this, locals);
19524 },
19525
19526 /**
19527 * @ngdoc method
19528 * @name $rootScope.Scope#$evalAsync
19529 * @kind function
19530 *
19531 * @description
19532 * Executes the expression on the current scope at a later point in time.
19533 *
19534 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
19535 * that:
19536 *
19537 * - it will execute after the function that scheduled the evaluation (preferably before DOM
19538 * rendering).
19539 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
19540 * `expression` execution.
19541 *
19542 * Any exceptions from the execution of the expression are forwarded to the
19543 * {@link ng.$exceptionHandler $exceptionHandler} service.
19544 *
19545 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
19546 * will be scheduled. However, it is encouraged to always call code that changes the model
19547 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
19548 *
19549 * @param {(string|function())=} expression An AngularJS expression to be executed.
19550 *
19551 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
19552 * - `function(scope)`: execute the function with the current `scope` parameter.
19553 *
19554 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
19555 */
19556 $evalAsync: function(expr, locals) {
19557 // if we are outside of an $digest loop and this is the first time we are scheduling async
19558 // task also schedule async auto-flush
19559 if (!$rootScope.$$phase && !asyncQueue.length) {
19560 $browser.defer(function() {
19561 if (asyncQueue.length) {
19562 $rootScope.$digest();
19563 }
19564 }, null, '$evalAsync');
19565 }
19566
19567 asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
19568 },
19569
19570 $$postDigest: function(fn) {
19571 postDigestQueue.push(fn);
19572 },
19573
19574 /**
19575 * @ngdoc method
19576 * @name $rootScope.Scope#$apply
19577 * @kind function
19578 *
19579 * @description
19580 * `$apply()` is used to execute an expression in AngularJS from outside of the AngularJS
19581 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
19582 * Because we are calling into the AngularJS framework we need to perform proper scope life
19583 * cycle of {@link ng.$exceptionHandler exception handling},
19584 * {@link ng.$rootScope.Scope#$digest executing watches}.
19585 *
19586 * **Life cycle: Pseudo-Code of `$apply()`**
19587 *
19588 * ```js
19589 function $apply(expr) {
19590 try {
19591 return $eval(expr);
19592 } catch (e) {
19593 $exceptionHandler(e);
19594 } finally {
19595 $root.$digest();
19596 }
19597 }
19598 * ```
19599 *
19600 *
19601 * Scope's `$apply()` method transitions through the following stages:
19602 *
19603 * 1. The {@link guide/expression expression} is executed using the
19604 * {@link ng.$rootScope.Scope#$eval $eval()} method.
19605 * 2. Any exceptions from the execution of the expression are forwarded to the
19606 * {@link ng.$exceptionHandler $exceptionHandler} service.
19607 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
19608 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
19609 *
19610 *
19611 * @param {(string|function())=} exp An AngularJS expression to be executed.
19612 *
19613 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
19614 * - `function(scope)`: execute the function with current `scope` parameter.
19615 *
19616 * @returns {*} The result of evaluating the expression.
19617 */
19618 $apply: function(expr) {
19619 try {
19620 beginPhase('$apply');
19621 try {
19622 return this.$eval(expr);
19623 } finally {
19624 clearPhase();
19625 }
19626 } catch (e) {
19627 $exceptionHandler(e);
19628 } finally {
19629 try {
19630 $rootScope.$digest();
19631 } catch (e) {
19632 $exceptionHandler(e);
19633 // eslint-disable-next-line no-unsafe-finally
19634 throw e;
19635 }
19636 }
19637 },
19638
19639 /**
19640 * @ngdoc method
19641 * @name $rootScope.Scope#$applyAsync
19642 * @kind function
19643 *
19644 * @description
19645 * Schedule the invocation of $apply to occur at a later time. The actual time difference
19646 * varies across browsers, but is typically around ~10 milliseconds.
19647 *
19648 * This can be used to queue up multiple expressions which need to be evaluated in the same
19649 * digest.
19650 *
19651 * @param {(string|function())=} exp An AngularJS expression to be executed.
19652 *
19653 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
19654 * - `function(scope)`: execute the function with current `scope` parameter.
19655 */
19656 $applyAsync: function(expr) {
19657 var scope = this;
19658 if (expr) {
19659 applyAsyncQueue.push($applyAsyncExpression);
19660 }
19661 expr = $parse(expr);
19662 scheduleApplyAsync();
19663
19664 function $applyAsyncExpression() {
19665 scope.$eval(expr);
19666 }
19667 },
19668
19669 /**
19670 * @ngdoc method
19671 * @name $rootScope.Scope#$on
19672 * @kind function
19673 *
19674 * @description
19675 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
19676 * discussion of event life cycle.
19677 *
19678 * The event listener function format is: `function(event, args...)`. The `event` object
19679 * passed into the listener has the following attributes:
19680 *
19681 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
19682 * `$broadcast`-ed.
19683 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
19684 * event propagates through the scope hierarchy, this property is set to null.
19685 * - `name` - `{string}`: name of the event.
19686 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
19687 * further event propagation (available only for events that were `$emit`-ed).
19688 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
19689 * to true.
19690 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
19691 *
19692 * @param {string} name Event name to listen on.
19693 * @param {function(event, ...args)} listener Function to call when the event is emitted.
19694 * @returns {function()} Returns a deregistration function for this listener.
19695 */
19696 $on: function(name, listener) {
19697 var namedListeners = this.$$listeners[name];
19698 if (!namedListeners) {
19699 this.$$listeners[name] = namedListeners = [];
19700 }
19701 namedListeners.push(listener);
19702
19703 var current = this;
19704 do {
19705 if (!current.$$listenerCount[name]) {
19706 current.$$listenerCount[name] = 0;
19707 }
19708 current.$$listenerCount[name]++;
19709 } while ((current = current.$parent));
19710
19711 var self = this;
19712 return function() {
19713 var indexOfListener = namedListeners.indexOf(listener);
19714 if (indexOfListener !== -1) {
19715 // Use delete in the hope of the browser deallocating the memory for the array entry,
19716 // while not shifting the array indexes of other listeners.
19717 // See issue https://github.com/angular/angular.js/issues/16135
19718 delete namedListeners[indexOfListener];
19719 decrementListenerCount(self, 1, name);
19720 }
19721 };
19722 },
19723
19724
19725 /**
19726 * @ngdoc method
19727 * @name $rootScope.Scope#$emit
19728 * @kind function
19729 *
19730 * @description
19731 * Dispatches an event `name` upwards through the scope hierarchy notifying the
19732 * registered {@link ng.$rootScope.Scope#$on} listeners.
19733 *
19734 * The event life cycle starts at the scope on which `$emit` was called. All
19735 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
19736 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
19737 * registered listeners along the way. The event will stop propagating if one of the listeners
19738 * cancels it.
19739 *
19740 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
19741 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
19742 *
19743 * @param {string} name Event name to emit.
19744 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
19745 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
19746 */
19747 $emit: function(name, args) {
19748 var empty = [],
19749 namedListeners,
19750 scope = this,
19751 stopPropagation = false,
19752 event = {
19753 name: name,
19754 targetScope: scope,
19755 stopPropagation: function() {stopPropagation = true;},
19756 preventDefault: function() {
19757 event.defaultPrevented = true;
19758 },
19759 defaultPrevented: false
19760 },
19761 listenerArgs = concat([event], arguments, 1),
19762 i, length;
19763
19764 do {
19765 namedListeners = scope.$$listeners[name] || empty;
19766 event.currentScope = scope;
19767 for (i = 0, length = namedListeners.length; i < length; i++) {
19768
19769 // if listeners were deregistered, defragment the array
19770 if (!namedListeners[i]) {
19771 namedListeners.splice(i, 1);
19772 i--;
19773 length--;
19774 continue;
19775 }
19776 try {
19777 //allow all listeners attached to the current scope to run
19778 namedListeners[i].apply(null, listenerArgs);
19779 } catch (e) {
19780 $exceptionHandler(e);
19781 }
19782 }
19783 //if any listener on the current scope stops propagation, prevent bubbling
19784 if (stopPropagation) {
19785 break;
19786 }
19787 //traverse upwards
19788 scope = scope.$parent;
19789 } while (scope);
19790
19791 event.currentScope = null;
19792
19793 return event;
19794 },
19795
19796
19797 /**
19798 * @ngdoc method
19799 * @name $rootScope.Scope#$broadcast
19800 * @kind function
19801 *
19802 * @description
19803 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
19804 * registered {@link ng.$rootScope.Scope#$on} listeners.
19805 *
19806 * The event life cycle starts at the scope on which `$broadcast` was called. All
19807 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
19808 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
19809 * scope and calls all registered listeners along the way. The event cannot be canceled.
19810 *
19811 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
19812 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
19813 *
19814 * @param {string} name Event name to broadcast.
19815 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
19816 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
19817 */
19818 $broadcast: function(name, args) {
19819 var target = this,
19820 current = target,
19821 next = target,
19822 event = {
19823 name: name,
19824 targetScope: target,
19825 preventDefault: function() {
19826 event.defaultPrevented = true;
19827 },
19828 defaultPrevented: false
19829 };
19830
19831 if (!target.$$listenerCount[name]) return event;
19832
19833 var listenerArgs = concat([event], arguments, 1),
19834 listeners, i, length;
19835
19836 //down while you can, then up and next sibling or up and next sibling until back at root
19837 while ((current = next)) {
19838 event.currentScope = current;
19839 listeners = current.$$listeners[name] || [];
19840 for (i = 0, length = listeners.length; i < length; i++) {
19841 // if listeners were deregistered, defragment the array
19842 if (!listeners[i]) {
19843 listeners.splice(i, 1);
19844 i--;
19845 length--;
19846 continue;
19847 }
19848
19849 try {
19850 listeners[i].apply(null, listenerArgs);
19851 } catch (e) {
19852 $exceptionHandler(e);
19853 }
19854 }
19855
19856 // Insanity Warning: scope depth-first traversal
19857 // yes, this code is a bit crazy, but it works and we have tests to prove it!
19858 // this piece should be kept in sync with the traversal in $digest
19859 // (though it differs due to having the extra check for $$listenerCount and
19860 // does not check $$suspended)
19861 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
19862 (current !== target && current.$$nextSibling)))) {
19863 while (current !== target && !(next = current.$$nextSibling)) {
19864 current = current.$parent;
19865 }
19866 }
19867 }
19868
19869 event.currentScope = null;
19870 return event;
19871 }
19872 };
19873
19874 var $rootScope = new Scope();
19875
19876 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
19877 var asyncQueue = $rootScope.$$asyncQueue = [];
19878 var postDigestQueue = $rootScope.$$postDigestQueue = [];
19879 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
19880
19881 var postDigestQueuePosition = 0;
19882
19883 return $rootScope;
19884
19885
19886 function beginPhase(phase) {
19887 if ($rootScope.$$phase) {
19888 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
19889 }
19890
19891 $rootScope.$$phase = phase;
19892 }
19893
19894 function clearPhase() {
19895 $rootScope.$$phase = null;
19896 }
19897
19898 function incrementWatchersCount(current, count) {
19899 do {
19900 current.$$watchersCount += count;
19901 } while ((current = current.$parent));
19902 }
19903
19904 function decrementListenerCount(current, count, name) {
19905 do {
19906 current.$$listenerCount[name] -= count;
19907
19908 if (current.$$listenerCount[name] === 0) {
19909 delete current.$$listenerCount[name];
19910 }
19911 } while ((current = current.$parent));
19912 }
19913
19914 /**
19915 * function used as an initial value for watchers.
19916 * because it's unique we can easily tell it apart from other values
19917 */
19918 function initWatchVal() {}
19919
19920 function flushApplyAsync() {
19921 while (applyAsyncQueue.length) {
19922 try {
19923 applyAsyncQueue.shift()();
19924 } catch (e) {
19925 $exceptionHandler(e);
19926 }
19927 }
19928 applyAsyncId = null;
19929 }
19930
19931 function scheduleApplyAsync() {
19932 if (applyAsyncId === null) {
19933 applyAsyncId = $browser.defer(function() {
19934 $rootScope.$apply(flushApplyAsync);
19935 }, null, '$applyAsync');
19936 }
19937 }
19938 }];
19939}
19940
19941/**
19942 * @ngdoc service
19943 * @name $rootElement
19944 *
19945 * @description
19946 * The root element of AngularJS application. This is either the element where {@link
19947 * ng.directive:ngApp ngApp} was declared or the element passed into
19948 * {@link angular.bootstrap}. The element represents the root element of application. It is also the
19949 * location where the application's {@link auto.$injector $injector} service gets
19950 * published, and can be retrieved using `$rootElement.injector()`.
19951 */
19952
19953
19954// the implementation is in angular.bootstrap
19955
19956/**
19957 * @this
19958 * @description
19959 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
19960 */
19961function $$SanitizeUriProvider() {
19962
19963 var aHrefSanitizationTrustedUrlList = /^\s*(https?|s?ftp|mailto|tel|file):/,
19964 imgSrcSanitizationTrustedUrlList = /^\s*((https?|ftp|file|blob):|data:image\/)/;
19965
19966 /**
19967 * @description
19968 * Retrieves or overrides the default regular expression that is used for determining trusted safe
19969 * urls during a[href] sanitization.
19970 *
19971 * The sanitization is a security measure aimed at prevent XSS attacks via HTML anchor links.
19972 *
19973 * Any url due to be assigned to an `a[href]` attribute via interpolation is marked as requiring
19974 * the $sce.URL security context. When interpolation occurs a call is made to `$sce.trustAsUrl(url)`
19975 * which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize the potentially malicious URL.
19976 *
19977 * If the URL matches the `aHrefSanitizationTrustedUrlList` regular expression, it is returned unchanged.
19978 *
19979 * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written
19980 * to the DOM it is inactive and potentially malicious code will not be executed.
19981 *
19982 * @param {RegExp=} regexp New regexp to trust urls with.
19983 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
19984 * chaining otherwise.
19985 */
19986 this.aHrefSanitizationTrustedUrlList = function(regexp) {
19987 if (isDefined(regexp)) {
19988 aHrefSanitizationTrustedUrlList = regexp;
19989 return this;
19990 }
19991 return aHrefSanitizationTrustedUrlList;
19992 };
19993
19994
19995 /**
19996 * @description
19997 * Retrieves or overrides the default regular expression that is used for determining trusted safe
19998 * urls during img[src] sanitization.
19999 *
20000 * The sanitization is a security measure aimed at prevent XSS attacks via HTML image src links.
20001 *
20002 * Any URL due to be assigned to an `img[src]` attribute via interpolation is marked as requiring
20003 * the $sce.MEDIA_URL security context. When interpolation occurs a call is made to
20004 * `$sce.trustAsMediaUrl(url)` which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize
20005 * the potentially malicious URL.
20006 *
20007 * If the URL matches the `imgSrcSanitizationTrustedUrlList` regular expression, it is returned
20008 * unchanged.
20009 *
20010 * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written
20011 * to the DOM it is inactive and potentially malicious code will not be executed.
20012 *
20013 * @param {RegExp=} regexp New regexp to trust urls with.
20014 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
20015 * chaining otherwise.
20016 */
20017 this.imgSrcSanitizationTrustedUrlList = function(regexp) {
20018 if (isDefined(regexp)) {
20019 imgSrcSanitizationTrustedUrlList = regexp;
20020 return this;
20021 }
20022 return imgSrcSanitizationTrustedUrlList;
20023 };
20024
20025 this.$get = function() {
20026 return function sanitizeUri(uri, isMediaUrl) {
20027 // if (!uri) return uri;
20028 var regex = isMediaUrl ? imgSrcSanitizationTrustedUrlList : aHrefSanitizationTrustedUrlList;
20029 var normalizedVal = urlResolve(uri && uri.trim()).href;
20030 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
20031 return 'unsafe:' + normalizedVal;
20032 }
20033 return uri;
20034 };
20035 };
20036}
20037
20038/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
20039 * Any commits to this file should be reviewed with security in mind. *
20040 * Changes to this file can potentially create security vulnerabilities. *
20041 * An approval from 2 Core members with history of modifying *
20042 * this file is required. *
20043 * *
20044 * Does the change somehow allow for arbitrary javascript to be executed? *
20045 * Or allows for someone to change the prototype of built-in objects? *
20046 * Or gives undesired access to variables likes document or window? *
20047 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
20048
20049/* exported $SceProvider, $SceDelegateProvider */
20050
20051var $sceMinErr = minErr('$sce');
20052
20053var SCE_CONTEXTS = {
20054 // HTML is used when there's HTML rendered (e.g. ng-bind-html, iframe srcdoc binding).
20055 HTML: 'html',
20056
20057 // Style statements or stylesheets. Currently unused in AngularJS.
20058 CSS: 'css',
20059
20060 // An URL used in a context where it refers to the source of media, which are not expected to be run
20061 // as scripts, such as an image, audio, video, etc.
20062 MEDIA_URL: 'mediaUrl',
20063
20064 // An URL used in a context where it does not refer to a resource that loads code.
20065 // A value that can be trusted as a URL can also trusted as a MEDIA_URL.
20066 URL: 'url',
20067
20068 // RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as
20069 // code. (e.g. ng-include, script src binding, templateUrl)
20070 // A value that can be trusted as a RESOURCE_URL, can also trusted as a URL and a MEDIA_URL.
20071 RESOURCE_URL: 'resourceUrl',
20072
20073 // Script. Currently unused in AngularJS.
20074 JS: 'js'
20075};
20076
20077// Helper functions follow.
20078
20079var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g;
20080
20081function snakeToCamel(name) {
20082 return name
20083 .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
20084}
20085
20086function adjustMatcher(matcher) {
20087 if (matcher === 'self') {
20088 return matcher;
20089 } else if (isString(matcher)) {
20090 // Strings match exactly except for 2 wildcards - '*' and '**'.
20091 // '*' matches any character except those from the set ':/.?&'.
20092 // '**' matches any character (like .* in a RegExp).
20093 // More than 2 *'s raises an error as it's ill defined.
20094 if (matcher.indexOf('***') > -1) {
20095 throw $sceMinErr('iwcard',
20096 'Illegal sequence *** in string matcher. String: {0}', matcher);
20097 }
20098 matcher = escapeForRegexp(matcher).
20099 replace(/\\\*\\\*/g, '.*').
20100 replace(/\\\*/g, '[^:/.?&;]*');
20101 return new RegExp('^' + matcher + '$');
20102 } else if (isRegExp(matcher)) {
20103 // The only other type of matcher allowed is a Regexp.
20104 // Match entire URL / disallow partial matches.
20105 // Flags are reset (i.e. no global, ignoreCase or multiline)
20106 return new RegExp('^' + matcher.source + '$');
20107 } else {
20108 throw $sceMinErr('imatcher',
20109 'Matchers may only be "self", string patterns or RegExp objects');
20110 }
20111}
20112
20113
20114function adjustMatchers(matchers) {
20115 var adjustedMatchers = [];
20116 if (isDefined(matchers)) {
20117 forEach(matchers, function(matcher) {
20118 adjustedMatchers.push(adjustMatcher(matcher));
20119 });
20120 }
20121 return adjustedMatchers;
20122}
20123
20124
20125/**
20126 * @ngdoc service
20127 * @name $sceDelegate
20128 * @kind function
20129 *
20130 * @description
20131 *
20132 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
20133 * Contextual Escaping (SCE)} services to AngularJS.
20134 *
20135 * For an overview of this service and the functionnality it provides in AngularJS, see the main
20136 * page for {@link ng.$sce SCE}. The current page is targeted for developers who need to alter how
20137 * SCE works in their application, which shouldn't be needed in most cases.
20138 *
20139 * <div class="alert alert-danger">
20140 * AngularJS strongly relies on contextual escaping for the security of bindings: disabling or
20141 * modifying this might cause cross site scripting (XSS) vulnerabilities. For libraries owners,
20142 * changes to this service will also influence users, so be extra careful and document your changes.
20143 * </div>
20144 *
20145 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
20146 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
20147 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
20148 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
20149 * work because `$sce` delegates to `$sceDelegate` for these operations.
20150 *
20151 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
20152 *
20153 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
20154 * can override it completely to change the behavior of `$sce`, the common case would
20155 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
20156 * your own trusted and banned resource lists for trusting URLs used for loading AngularJS resources
20157 * such as templates. Refer {@link ng.$sceDelegateProvider#trustedResourceUrlList
20158 * $sceDelegateProvider.trustedResourceUrlList} and {@link
20159 * ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList}
20160 */
20161
20162/**
20163 * @ngdoc provider
20164 * @name $sceDelegateProvider
20165 * @this
20166 *
20167 * @description
20168 *
20169 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
20170 * $sceDelegate service}, used as a delegate for {@link ng.$sce Strict Contextual Escaping (SCE)}.
20171 *
20172 * The `$sceDelegateProvider` allows one to get/set the `trustedResourceUrlList` and
20173 * `bannedResourceUrlList` used to ensure that the URLs used for sourcing AngularJS templates and
20174 * other script-running URLs are safe (all places that use the `$sce.RESOURCE_URL` context). See
20175 * {@link ng.$sceDelegateProvider#trustedResourceUrlList
20176 * $sceDelegateProvider.trustedResourceUrlList} and
20177 * {@link ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList},
20178 *
20179 * For the general details about this service in AngularJS, read the main page for {@link ng.$sce
20180 * Strict Contextual Escaping (SCE)}.
20181 *
20182 * **Example**: Consider the following case. <a name="example"></a>
20183 *
20184 * - your app is hosted at url `http://myapp.example.com/`
20185 * - but some of your templates are hosted on other domains you control such as
20186 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
20187 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
20188 *
20189 * Here is what a secure configuration for this scenario might look like:
20190 *
20191 * ```
20192 * angular.module('myApp', []).config(function($sceDelegateProvider) {
20193 * $sceDelegateProvider.trustedResourceUrlList([
20194 * // Allow same origin resource loads.
20195 * 'self',
20196 * // Allow loading from our assets domain. Notice the difference between * and **.
20197 * 'http://srv*.assets.example.com/**'
20198 * ]);
20199 *
20200 * // The banned resource URL list overrides the trusted resource URL list so the open redirect
20201 * // here is blocked.
20202 * $sceDelegateProvider.bannedResourceUrlList([
20203 * 'http://myapp.example.com/clickThru**'
20204 * ]);
20205 * });
20206 * ```
20207 * Note that an empty trusted resource URL list will block every resource URL from being loaded, and will require
20208 * you to manually mark each one as trusted with `$sce.trustAsResourceUrl`. However, templates
20209 * requested by {@link ng.$templateRequest $templateRequest} that are present in
20210 * {@link ng.$templateCache $templateCache} will not go through this check. If you have a mechanism
20211 * to populate your templates in that cache at config time, then it is a good idea to remove 'self'
20212 * from the trusted resource URL lsit. This helps to mitigate the security impact of certain types
20213 * of issues, like for instance attacker-controlled `ng-includes`.
20214 */
20215
20216function $SceDelegateProvider() {
20217 this.SCE_CONTEXTS = SCE_CONTEXTS;
20218
20219 // Resource URLs can also be trusted by policy.
20220 var trustedResourceUrlList = ['self'],
20221 bannedResourceUrlList = [];
20222
20223 /**
20224 * @ngdoc method
20225 * @name $sceDelegateProvider#trustedResourceUrlList
20226 * @kind function
20227 *
20228 * @param {Array=} trustedResourceUrlList When provided, replaces the trustedResourceUrlList with
20229 * the value provided. This must be an array or null. A snapshot of this array is used so
20230 * further changes to the array are ignored.
20231 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
20232 * allowed in this array.
20233 *
20234 * @return {Array} The currently set trusted resource URL array.
20235 *
20236 * @description
20237 * Sets/Gets the list trusted of resource URLs.
20238 *
20239 * The **default value** when no `trustedResourceUrlList` has been explicitly set is `['self']`
20240 * allowing only same origin resource requests.
20241 *
20242 * <div class="alert alert-warning">
20243 * **Note:** the default `trustedResourceUrlList` of 'self' is not recommended if your app shares
20244 * its origin with other apps! It is a good idea to limit it to only your application's directory.
20245 * </div>
20246 */
20247 this.trustedResourceUrlList = function(value) {
20248 if (arguments.length) {
20249 trustedResourceUrlList = adjustMatchers(value);
20250 }
20251 return trustedResourceUrlList;
20252 };
20253
20254 /**
20255 * @ngdoc method
20256 * @name $sceDelegateProvider#resourceUrlWhitelist
20257 * @kind function
20258 *
20259 * @deprecated
20260 * sinceVersion="1.8.1"
20261 *
20262 * This method is deprecated. Use {@link $sceDelegateProvider#trustedResourceUrlList
20263 * trustedResourceUrlList} instead.
20264 */
20265 Object.defineProperty(this, 'resourceUrlWhitelist', {
20266 get: function() {
20267 return this.trustedResourceUrlList;
20268 },
20269 set: function(value) {
20270 this.trustedResourceUrlList = value;
20271 }
20272 });
20273
20274 /**
20275 * @ngdoc method
20276 * @name $sceDelegateProvider#bannedResourceUrlList
20277 * @kind function
20278 *
20279 * @param {Array=} bannedResourceUrlList When provided, replaces the `bannedResourceUrlList` with
20280 * the value provided. This must be an array or null. A snapshot of this array is used so
20281 * further changes to the array are ignored.</p><p>
20282 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
20283 * allowed in this array.</p><p>
20284 * The typical usage for the `bannedResourceUrlList` is to **block
20285 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
20286 * these would otherwise be trusted but actually return content from the redirected domain.
20287 * </p><p>
20288 * Finally, **the banned resource URL list overrides the trusted resource URL list** and has
20289 * the final say.
20290 *
20291 * @return {Array} The currently set `bannedResourceUrlList` array.
20292 *
20293 * @description
20294 * Sets/Gets the `bannedResourceUrlList` of trusted resource URLs.
20295 *
20296 * The **default value** when no trusted resource URL list has been explicitly set is the empty
20297 * array (i.e. there is no `bannedResourceUrlList`.)
20298 */
20299 this.bannedResourceUrlList = function(value) {
20300 if (arguments.length) {
20301 bannedResourceUrlList = adjustMatchers(value);
20302 }
20303 return bannedResourceUrlList;
20304 };
20305
20306 /**
20307 * @ngdoc method
20308 * @name $sceDelegateProvider#resourceUrlBlacklist
20309 * @kind function
20310 *
20311 * @deprecated
20312 * sinceVersion="1.8.1"
20313 *
20314 * This method is deprecated. Use {@link $sceDelegateProvider#bannedResourceUrlList
20315 * bannedResourceUrlList} instead.
20316 */
20317 Object.defineProperty(this, 'resourceUrlBlacklist', {
20318 get: function() {
20319 return this.bannedResourceUrlList;
20320 },
20321 set: function(value) {
20322 this.bannedResourceUrlList = value;
20323 }
20324 });
20325
20326 this.$get = ['$injector', '$$sanitizeUri', function($injector, $$sanitizeUri) {
20327
20328 var htmlSanitizer = function htmlSanitizer(html) {
20329 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
20330 };
20331
20332 if ($injector.has('$sanitize')) {
20333 htmlSanitizer = $injector.get('$sanitize');
20334 }
20335
20336
20337 function matchUrl(matcher, parsedUrl) {
20338 if (matcher === 'self') {
20339 return urlIsSameOrigin(parsedUrl) || urlIsSameOriginAsBaseUrl(parsedUrl);
20340 } else {
20341 // definitely a regex. See adjustMatchers()
20342 return !!matcher.exec(parsedUrl.href);
20343 }
20344 }
20345
20346 function isResourceUrlAllowedByPolicy(url) {
20347 var parsedUrl = urlResolve(url.toString());
20348 var i, n, allowed = false;
20349 // Ensure that at least one item from the trusted resource URL list allows this url.
20350 for (i = 0, n = trustedResourceUrlList.length; i < n; i++) {
20351 if (matchUrl(trustedResourceUrlList[i], parsedUrl)) {
20352 allowed = true;
20353 break;
20354 }
20355 }
20356 if (allowed) {
20357 // Ensure that no item from the banned resource URL list has blocked this url.
20358 for (i = 0, n = bannedResourceUrlList.length; i < n; i++) {
20359 if (matchUrl(bannedResourceUrlList[i], parsedUrl)) {
20360 allowed = false;
20361 break;
20362 }
20363 }
20364 }
20365 return allowed;
20366 }
20367
20368 function generateHolderType(Base) {
20369 var holderType = function TrustedValueHolderType(trustedValue) {
20370 this.$$unwrapTrustedValue = function() {
20371 return trustedValue;
20372 };
20373 };
20374 if (Base) {
20375 holderType.prototype = new Base();
20376 }
20377 holderType.prototype.valueOf = function sceValueOf() {
20378 return this.$$unwrapTrustedValue();
20379 };
20380 holderType.prototype.toString = function sceToString() {
20381 return this.$$unwrapTrustedValue().toString();
20382 };
20383 return holderType;
20384 }
20385
20386 var trustedValueHolderBase = generateHolderType(),
20387 byType = {};
20388
20389 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
20390 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
20391 byType[SCE_CONTEXTS.MEDIA_URL] = generateHolderType(trustedValueHolderBase);
20392 byType[SCE_CONTEXTS.URL] = generateHolderType(byType[SCE_CONTEXTS.MEDIA_URL]);
20393 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
20394 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
20395
20396 /**
20397 * @ngdoc method
20398 * @name $sceDelegate#trustAs
20399 *
20400 * @description
20401 * Returns a trusted representation of the parameter for the specified context. This trusted
20402 * object will later on be used as-is, without any security check, by bindings or directives
20403 * that require this security context.
20404 * For instance, marking a string as trusted for the `$sce.HTML` context will entirely bypass
20405 * the potential `$sanitize` call in corresponding `$sce.HTML` bindings or directives, such as
20406 * `ng-bind-html`. Note that in most cases you won't need to call this function: if you have the
20407 * sanitizer loaded, passing the value itself will render all the HTML that does not pose a
20408 * security risk.
20409 *
20410 * See {@link ng.$sceDelegate#getTrusted getTrusted} for the function that will consume those
20411 * trusted values, and {@link ng.$sce $sce} for general documentation about strict contextual
20412 * escaping.
20413 *
20414 * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
20415 * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
20416 *
20417 * @param {*} value The value that should be considered trusted.
20418 * @return {*} A trusted representation of value, that can be used in the given context.
20419 */
20420 function trustAs(type, trustedValue) {
20421 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
20422 if (!Constructor) {
20423 throw $sceMinErr('icontext',
20424 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
20425 type, trustedValue);
20426 }
20427 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
20428 return trustedValue;
20429 }
20430 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
20431 // mutable objects, we ensure here that the value passed in is actually a string.
20432 if (typeof trustedValue !== 'string') {
20433 throw $sceMinErr('itype',
20434 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
20435 type);
20436 }
20437 return new Constructor(trustedValue);
20438 }
20439
20440 /**
20441 * @ngdoc method
20442 * @name $sceDelegate#valueOf
20443 *
20444 * @description
20445 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
20446 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
20447 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
20448 *
20449 * If the passed parameter is not a value that had been returned by {@link
20450 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, it must be returned as-is.
20451 *
20452 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
20453 * call or anything else.
20454 * @return {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
20455 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
20456 * `value` unchanged.
20457 */
20458 function valueOf(maybeTrusted) {
20459 if (maybeTrusted instanceof trustedValueHolderBase) {
20460 return maybeTrusted.$$unwrapTrustedValue();
20461 } else {
20462 return maybeTrusted;
20463 }
20464 }
20465
20466 /**
20467 * @ngdoc method
20468 * @name $sceDelegate#getTrusted
20469 *
20470 * @description
20471 * Given an object and a security context in which to assign it, returns a value that's safe to
20472 * use in this context, which was represented by the parameter. To do so, this function either
20473 * unwraps the safe type it has been given (for instance, a {@link ng.$sceDelegate#trustAs
20474 * `$sceDelegate.trustAs`} result), or it might try to sanitize the value given, depending on
20475 * the context and sanitizer availablility.
20476 *
20477 * The contexts that can be sanitized are $sce.MEDIA_URL, $sce.URL and $sce.HTML. The first two are available
20478 * by default, and the third one relies on the `$sanitize` service (which may be loaded through
20479 * the `ngSanitize` module). Furthermore, for $sce.RESOURCE_URL context, a plain string may be
20480 * accepted if the resource url policy defined by {@link ng.$sceDelegateProvider#trustedResourceUrlList
20481 * `$sceDelegateProvider.trustedResourceUrlList`} and {@link ng.$sceDelegateProvider#bannedResourceUrlList
20482 * `$sceDelegateProvider.bannedResourceUrlList`} accepts that resource.
20483 *
20484 * This function will throw if the safe type isn't appropriate for this context, or if the
20485 * value given cannot be accepted in the context (which might be caused by sanitization not
20486 * being available, or the value not being recognized as safe).
20487 *
20488 * <div class="alert alert-danger">
20489 * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting
20490 * (XSS) vulnerability in your application.
20491 * </div>
20492 *
20493 * @param {string} type The context in which this value is to be used (such as `$sce.HTML`).
20494 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
20495 * `$sceDelegate.trustAs`} call, or anything else (which will not be considered trusted.)
20496 * @return {*} A version of the value that's safe to use in the given context, or throws an
20497 * exception if this is impossible.
20498 */
20499 function getTrusted(type, maybeTrusted) {
20500 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
20501 return maybeTrusted;
20502 }
20503 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
20504 // If maybeTrusted is a trusted class instance or subclass instance, then unwrap and return
20505 // as-is.
20506 if (constructor && maybeTrusted instanceof constructor) {
20507 return maybeTrusted.$$unwrapTrustedValue();
20508 }
20509
20510 // If maybeTrusted is a trusted class instance but not of the correct trusted type
20511 // then unwrap it and allow it to pass through to the rest of the checks
20512 if (isFunction(maybeTrusted.$$unwrapTrustedValue)) {
20513 maybeTrusted = maybeTrusted.$$unwrapTrustedValue();
20514 }
20515
20516 // If we get here, then we will either sanitize the value or throw an exception.
20517 if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) {
20518 // we attempt to sanitize non-resource URLs
20519 return $$sanitizeUri(maybeTrusted.toString(), type === SCE_CONTEXTS.MEDIA_URL);
20520 } else if (type === SCE_CONTEXTS.RESOURCE_URL) {
20521 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
20522 return maybeTrusted;
20523 } else {
20524 throw $sceMinErr('insecurl',
20525 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
20526 maybeTrusted.toString());
20527 }
20528 } else if (type === SCE_CONTEXTS.HTML) {
20529 // htmlSanitizer throws its own error when no sanitizer is available.
20530 return htmlSanitizer(maybeTrusted);
20531 }
20532 // Default error when the $sce service has no way to make the input safe.
20533 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
20534 }
20535
20536 return { trustAs: trustAs,
20537 getTrusted: getTrusted,
20538 valueOf: valueOf };
20539 }];
20540}
20541
20542
20543/**
20544 * @ngdoc provider
20545 * @name $sceProvider
20546 * @this
20547 *
20548 * @description
20549 *
20550 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
20551 * - enable/disable Strict Contextual Escaping (SCE) in a module
20552 * - override the default implementation with a custom delegate
20553 *
20554 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
20555 */
20556
20557/**
20558 * @ngdoc service
20559 * @name $sce
20560 * @kind function
20561 *
20562 * @description
20563 *
20564 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
20565 *
20566 * ## Strict Contextual Escaping
20567 *
20568 * Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render
20569 * trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and
20570 * (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
20571 *
20572 * ### Overview
20573 *
20574 * To systematically block XSS security bugs, AngularJS treats all values as untrusted by default in
20575 * HTML or sensitive URL bindings. When binding untrusted values, AngularJS will automatically
20576 * run security checks on them (sanitizations, trusted URL resource, depending on context), or throw
20577 * when it cannot guarantee the security of the result. That behavior depends strongly on contexts:
20578 * HTML can be sanitized, but template URLs cannot, for instance.
20579 *
20580 * To illustrate this, consider the `ng-bind-html` directive. It renders its value directly as HTML:
20581 * we call that the *context*. When given an untrusted input, AngularJS will attempt to sanitize it
20582 * before rendering if a sanitizer is available, and throw otherwise. To bypass sanitization and
20583 * render the input as-is, you will need to mark it as trusted for that context before attempting
20584 * to bind it.
20585 *
20586 * As of version 1.2, AngularJS ships with SCE enabled by default.
20587 *
20588 * ### In practice
20589 *
20590 * Here's an example of a binding in a privileged context:
20591 *
20592 * ```
20593 * <input ng-model="userHtml" aria-label="User input">
20594 * <div ng-bind-html="userHtml"></div>
20595 * ```
20596 *
20597 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
20598 * disabled, this application allows the user to render arbitrary HTML into the DIV, which would
20599 * be an XSS security bug. In a more realistic example, one may be rendering user comments, blog
20600 * articles, etc. via bindings. (HTML is just one example of a context where rendering user
20601 * controlled input creates security vulnerabilities.)
20602 *
20603 * For the case of HTML, you might use a library, either on the client side, or on the server side,
20604 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
20605 *
20606 * How would you ensure that every place that used these types of bindings was bound to a value that
20607 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
20608 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
20609 * properties/fields and forgot to update the binding to the sanitized value?
20610 *
20611 * To be secure by default, AngularJS makes sure bindings go through that sanitization, or
20612 * any similar validation process, unless there's a good reason to trust the given value in this
20613 * context. That trust is formalized with a function call. This means that as a developer, you
20614 * can assume all untrusted bindings are safe. Then, to audit your code for binding security issues,
20615 * you just need to ensure the values you mark as trusted indeed are safe - because they were
20616 * received from your server, sanitized by your library, etc. You can organize your codebase to
20617 * help with this - perhaps allowing only the files in a specific directory to do this.
20618 * Ensuring that the internal API exposed by that code doesn't markup arbitrary values as safe then
20619 * becomes a more manageable task.
20620 *
20621 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
20622 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
20623 * build the trusted versions of your values.
20624 *
20625 * ### How does it work?
20626 *
20627 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
20628 * $sce.getTrusted(context, value)} rather than to the value directly. Think of this function as
20629 * a way to enforce the required security context in your data sink. Directives use {@link
20630 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs
20631 * the {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. Also,
20632 * when binding without directives, AngularJS will understand the context of your bindings
20633 * automatically.
20634 *
20635 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
20636 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
20637 * simplified):
20638 *
20639 * ```
20640 * var ngBindHtmlDirective = ['$sce', function($sce) {
20641 * return function(scope, element, attr) {
20642 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
20643 * element.html(value || '');
20644 * });
20645 * };
20646 * }];
20647 * ```
20648 *
20649 * ### Impact on loading templates
20650 *
20651 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
20652 * `templateUrl`'s specified by {@link guide/directive directives}.
20653 *
20654 * By default, AngularJS only loads templates from the same domain and protocol as the application
20655 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
20656 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
20657 * protocols, you may either add them to the {@link ng.$sceDelegateProvider#trustedResourceUrlList
20658 * trustedResourceUrlList} or {@link ng.$sce#trustAsResourceUrl wrap them} into trusted values.
20659 *
20660 * *Please note*:
20661 * The browser's
20662 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
20663 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
20664 * policy apply in addition to this and may further restrict whether the template is successfully
20665 * loaded. This means that without the right CORS policy, loading templates from a different domain
20666 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
20667 * browsers.
20668 *
20669 * ### This feels like too much overhead
20670 *
20671 * It's important to remember that SCE only applies to interpolation expressions.
20672 *
20673 * If your expressions are constant literals, they're automatically trusted and you don't need to
20674 * call `$sce.trustAs` on them (e.g.
20675 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works (remember to include the
20676 * `ngSanitize` module). The `$sceDelegate` will also use the `$sanitize` service if it is available
20677 * when binding untrusted values to `$sce.HTML` context.
20678 * AngularJS provides an implementation in `angular-sanitize.js`, and if you
20679 * wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in
20680 * your application.
20681 *
20682 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
20683 * templates in `ng-include` from your application's domain without having to even know about SCE.
20684 * It blocks loading templates from other domains or loading templates over http from an https
20685 * served document. You can change these by setting your own custom {@link
20686 * ng.$sceDelegateProvider#trustedResourceUrlList trusted resource URL list} and {@link
20687 * ng.$sceDelegateProvider#bannedResourceUrlList banned resource URL list} for matching such URLs.
20688 *
20689 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
20690 * application that's secure and can be audited to verify that with much more ease than bolting
20691 * security onto an application later.
20692 *
20693 * <a name="contexts"></a>
20694 * ### What trusted context types are supported?
20695 *
20696 * | Context | Notes |
20697 * |---------------------|----------------|
20698 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. |
20699 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
20700 * | `$sce.MEDIA_URL` | For URLs that are safe to render as media. Is automatically converted from string by sanitizing when needed. |
20701 * | `$sce.URL` | For URLs that are safe to follow as links. Is automatically converted from string by sanitizing when needed. Note that `$sce.URL` makes a stronger statement about the URL than `$sce.MEDIA_URL` does and therefore contexts requiring values trusted for `$sce.URL` can be used anywhere that values trusted for `$sce.MEDIA_URL` are required.|
20702 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required. <br><br> The {@link $sceDelegateProvider#trustedResourceUrlList $sceDelegateProvider#trustedResourceUrlList()} and {@link $sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider#bannedResourceUrlList()} can be used to restrict trusted origins for `RESOURCE_URL` |
20703 * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. |
20704 *
20705 *
20706 * <div class="alert alert-warning">
20707 * Be aware that, before AngularJS 1.7.0, `a[href]` and `img[src]` used to sanitize their
20708 * interpolated values directly rather than rely upon {@link ng.$sce#getTrusted `$sce.getTrusted`}.
20709 *
20710 * **As of 1.7.0, this is no longer the case.**
20711 *
20712 * Now such interpolations are marked as requiring `$sce.URL` (for `a[href]`) or `$sce.MEDIA_URL`
20713 * (for `img[src]`), so that the sanitization happens (via `$sce.getTrusted...`) when the `$interpolate`
20714 * service evaluates the expressions.
20715 * </div>
20716 *
20717 * There are no CSS or JS context bindings in AngularJS currently, so their corresponding `$sce.trustAs`
20718 * functions aren't useful yet. This might evolve.
20719 *
20720 * ### Format of items in {@link ng.$sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList}/{@link ng.$sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList} <a name="resourceUrlPatternItem"></a>
20721 *
20722 * Each element in these arrays must be one of the following:
20723 *
20724 * - **'self'**
20725 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
20726 * domain** as the application document using the **same protocol**.
20727 * - **String** (except the special value `'self'`)
20728 * - The string is matched against the full *normalized / absolute URL* of the resource
20729 * being tested (substring matches are not good enough.)
20730 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
20731 * match themselves.
20732 * - `*`: matches zero or more occurrences of any character other than one of the following 6
20733 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
20734 * for matching resource URL lists.
20735 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
20736 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
20737 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
20738 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
20739 * http://foo.example.com/templates/**).
20740 * - **RegExp** (*see caveat below*)
20741 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
20742 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
20743 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
20744 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
20745 * small number of cases. A `.` character in the regex used when matching the scheme or a
20746 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
20747 * is highly recommended to use the string patterns and only fall back to regular expressions
20748 * as a last resort.
20749 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
20750 * matched against the **entire** *normalized / absolute URL* of the resource being tested
20751 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
20752 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
20753 * - If you are generating your JavaScript from some other templating engine (not
20754 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
20755 * remember to escape your regular expression (and be aware that you might need more than
20756 * one level of escaping depending on your templating engine and the way you interpolated
20757 * the value.) Do make use of your platform's escaping mechanism as it might be good
20758 * enough before coding your own. E.g. Ruby has
20759 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
20760 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
20761 * Javascript lacks a similar built in function for escaping. Take a look at Google
20762 * Closure library's [goog.string.regExpEscape(s)](
20763 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
20764 *
20765 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
20766 *
20767 * ### Show me an example using SCE.
20768 *
20769 * <example module="mySceApp" deps="angular-sanitize.js" name="sce-service">
20770 * <file name="index.html">
20771 * <div ng-controller="AppController as myCtrl">
20772 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
20773 * <b>User comments</b><br>
20774 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
20775 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
20776 * exploit.
20777 * <div class="well">
20778 * <div ng-repeat="userComment in myCtrl.userComments">
20779 * <b>{{userComment.name}}</b>:
20780 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
20781 * <br>
20782 * </div>
20783 * </div>
20784 * </div>
20785 * </file>
20786 *
20787 * <file name="script.js">
20788 * angular.module('mySceApp', ['ngSanitize'])
20789 * .controller('AppController', ['$http', '$templateCache', '$sce',
20790 * function AppController($http, $templateCache, $sce) {
20791 * var self = this;
20792 * $http.get('test_data.json', {cache: $templateCache}).then(function(response) {
20793 * self.userComments = response.data;
20794 * });
20795 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
20796 * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
20797 * 'sanitization.&quot;">Hover over this text.</span>');
20798 * }]);
20799 * </file>
20800 *
20801 * <file name="test_data.json">
20802 * [
20803 * { "name": "Alice",
20804 * "htmlComment":
20805 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
20806 * },
20807 * { "name": "Bob",
20808 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
20809 * }
20810 * ]
20811 * </file>
20812 *
20813 * <file name="protractor.js" type="protractor">
20814 * describe('SCE doc demo', function() {
20815 * it('should sanitize untrusted values', function() {
20816 * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML'))
20817 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
20818 * });
20819 *
20820 * it('should NOT sanitize explicitly trusted values', function() {
20821 * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe(
20822 * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
20823 * 'sanitization.&quot;">Hover over this text.</span>');
20824 * });
20825 * });
20826 * </file>
20827 * </example>
20828 *
20829 *
20830 *
20831 * ## Can I disable SCE completely?
20832 *
20833 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
20834 * for little coding overhead. It will be much harder to take an SCE disabled application and
20835 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
20836 * for cases where you have a lot of existing code that was written before SCE was introduced and
20837 * you're migrating them a module at a time. Also do note that this is an app-wide setting, so if
20838 * you are writing a library, you will cause security bugs applications using it.
20839 *
20840 * That said, here's how you can completely disable SCE:
20841 *
20842 * ```
20843 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
20844 * // Completely disable SCE. For demonstration purposes only!
20845 * // Do not use in new projects or libraries.
20846 * $sceProvider.enabled(false);
20847 * });
20848 * ```
20849 *
20850 */
20851
20852function $SceProvider() {
20853 var enabled = true;
20854
20855 /**
20856 * @ngdoc method
20857 * @name $sceProvider#enabled
20858 * @kind function
20859 *
20860 * @param {boolean=} value If provided, then enables/disables SCE application-wide.
20861 * @return {boolean} True if SCE is enabled, false otherwise.
20862 *
20863 * @description
20864 * Enables/disables SCE and returns the current value.
20865 */
20866 this.enabled = function(value) {
20867 if (arguments.length) {
20868 enabled = !!value;
20869 }
20870 return enabled;
20871 };
20872
20873
20874 /* Design notes on the default implementation for SCE.
20875 *
20876 * The API contract for the SCE delegate
20877 * -------------------------------------
20878 * The SCE delegate object must provide the following 3 methods:
20879 *
20880 * - trustAs(contextEnum, value)
20881 * This method is used to tell the SCE service that the provided value is OK to use in the
20882 * contexts specified by contextEnum. It must return an object that will be accepted by
20883 * getTrusted() for a compatible contextEnum and return this value.
20884 *
20885 * - valueOf(value)
20886 * For values that were not produced by trustAs(), return them as is. For values that were
20887 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
20888 * trustAs is wrapping the given values into some type, this operation unwraps it when given
20889 * such a value.
20890 *
20891 * - getTrusted(contextEnum, value)
20892 * This function should return the value that is safe to use in the context specified by
20893 * contextEnum or throw and exception otherwise.
20894 *
20895 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
20896 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
20897 * instance, an implementation could maintain a registry of all trusted objects by context. In
20898 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
20899 * return the same object passed in if it was found in the registry under a compatible context or
20900 * throw an exception otherwise. An implementation might only wrap values some of the time based
20901 * on some criteria. getTrusted() might return a value and not throw an exception for special
20902 * constants or objects even if not wrapped. All such implementations fulfill this contract.
20903 *
20904 *
20905 * A note on the inheritance model for SCE contexts
20906 * ------------------------------------------------
20907 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
20908 * is purely an implementation details.
20909 *
20910 * The contract is simply this:
20911 *
20912 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
20913 * will also succeed.
20914 *
20915 * Inheritance happens to capture this in a natural way. In some future, we may not use
20916 * inheritance anymore. That is OK because no code outside of sce.js and sceSpecs.js would need to
20917 * be aware of this detail.
20918 */
20919
20920 this.$get = ['$parse', '$sceDelegate', function(
20921 $parse, $sceDelegate) {
20922 // Support: IE 9-11 only
20923 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
20924 // the "expression(javascript expression)" syntax which is insecure.
20925 if (enabled && msie < 8) {
20926 throw $sceMinErr('iequirks',
20927 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
20928 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
20929 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
20930 }
20931
20932 var sce = shallowCopy(SCE_CONTEXTS);
20933
20934 /**
20935 * @ngdoc method
20936 * @name $sce#isEnabled
20937 * @kind function
20938 *
20939 * @return {Boolean} True if SCE is enabled, false otherwise. If you want to set the value, you
20940 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
20941 *
20942 * @description
20943 * Returns a boolean indicating if SCE is enabled.
20944 */
20945 sce.isEnabled = function() {
20946 return enabled;
20947 };
20948 sce.trustAs = $sceDelegate.trustAs;
20949 sce.getTrusted = $sceDelegate.getTrusted;
20950 sce.valueOf = $sceDelegate.valueOf;
20951
20952 if (!enabled) {
20953 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
20954 sce.valueOf = identity;
20955 }
20956
20957 /**
20958 * @ngdoc method
20959 * @name $sce#parseAs
20960 *
20961 * @description
20962 * Converts AngularJS {@link guide/expression expression} into a function. This is like {@link
20963 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
20964 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
20965 * *result*)}
20966 *
20967 * @param {string} type The SCE context in which this result will be used.
20968 * @param {string} expression String expression to compile.
20969 * @return {function(context, locals)} A function which represents the compiled expression:
20970 *
20971 * * `context` – `{object}` – an object against which any expressions embedded in the
20972 * strings are evaluated against (typically a scope object).
20973 * * `locals` – `{object=}` – local variables context object, useful for overriding values
20974 * in `context`.
20975 */
20976 sce.parseAs = function sceParseAs(type, expr) {
20977 var parsed = $parse(expr);
20978 if (parsed.literal && parsed.constant) {
20979 return parsed;
20980 } else {
20981 return $parse(expr, function(value) {
20982 return sce.getTrusted(type, value);
20983 });
20984 }
20985 };
20986
20987 /**
20988 * @ngdoc method
20989 * @name $sce#trustAs
20990 *
20991 * @description
20992 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns a
20993 * wrapped object that represents your value, and the trust you have in its safety for the given
20994 * context. AngularJS can then use that value as-is in bindings of the specified secure context.
20995 * This is used in bindings for `ng-bind-html`, `ng-include`, and most `src` attribute
20996 * interpolations. See {@link ng.$sce $sce} for strict contextual escaping.
20997 *
20998 * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
20999 * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
21000 *
21001 * @param {*} value The value that that should be considered trusted.
21002 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
21003 * in the context you specified.
21004 */
21005
21006 /**
21007 * @ngdoc method
21008 * @name $sce#trustAsHtml
21009 *
21010 * @description
21011 * Shorthand method. `$sce.trustAsHtml(value)` →
21012 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
21013 *
21014 * @param {*} value The value to mark as trusted for `$sce.HTML` context.
21015 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
21016 * in `$sce.HTML` context (like `ng-bind-html`).
21017 */
21018
21019 /**
21020 * @ngdoc method
21021 * @name $sce#trustAsCss
21022 *
21023 * @description
21024 * Shorthand method. `$sce.trustAsCss(value)` →
21025 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.CSS, value)`}
21026 *
21027 * @param {*} value The value to mark as trusted for `$sce.CSS` context.
21028 * @return {*} A wrapped version of value that can be used as a trusted variant
21029 * of your `value` in `$sce.CSS` context. This context is currently unused, so there are
21030 * almost no reasons to use this function so far.
21031 */
21032
21033 /**
21034 * @ngdoc method
21035 * @name $sce#trustAsUrl
21036 *
21037 * @description
21038 * Shorthand method. `$sce.trustAsUrl(value)` →
21039 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
21040 *
21041 * @param {*} value The value to mark as trusted for `$sce.URL` context.
21042 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
21043 * in `$sce.URL` context. That context is currently unused, so there are almost no reasons
21044 * to use this function so far.
21045 */
21046
21047 /**
21048 * @ngdoc method
21049 * @name $sce#trustAsResourceUrl
21050 *
21051 * @description
21052 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
21053 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
21054 *
21055 * @param {*} value The value to mark as trusted for `$sce.RESOURCE_URL` context.
21056 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
21057 * in `$sce.RESOURCE_URL` context (template URLs in `ng-include`, most `src` attribute
21058 * bindings, ...)
21059 */
21060
21061 /**
21062 * @ngdoc method
21063 * @name $sce#trustAsJs
21064 *
21065 * @description
21066 * Shorthand method. `$sce.trustAsJs(value)` →
21067 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
21068 *
21069 * @param {*} value The value to mark as trusted for `$sce.JS` context.
21070 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
21071 * in `$sce.JS` context. That context is currently unused, so there are almost no reasons to
21072 * use this function so far.
21073 */
21074
21075 /**
21076 * @ngdoc method
21077 * @name $sce#getTrusted
21078 *
21079 * @description
21080 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
21081 * takes any input, and either returns a value that's safe to use in the specified context,
21082 * or throws an exception. This function is aware of trusted values created by the `trustAs`
21083 * function and its shorthands, and when contexts are appropriate, returns the unwrapped value
21084 * as-is. Finally, this function can also throw when there is no way to turn `maybeTrusted` in a
21085 * safe value (e.g., no sanitization is available or possible.)
21086 *
21087 * @param {string} type The context in which this value is to be used.
21088 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs
21089 * `$sce.trustAs`} call, or anything else (which will not be considered trusted.)
21090 * @return {*} A version of the value that's safe to use in the given context, or throws an
21091 * exception if this is impossible.
21092 */
21093
21094 /**
21095 * @ngdoc method
21096 * @name $sce#getTrustedHtml
21097 *
21098 * @description
21099 * Shorthand method. `$sce.getTrustedHtml(value)` →
21100 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
21101 *
21102 * @param {*} value The value to pass to `$sce.getTrusted`.
21103 * @return {*} The return value of `$sce.getTrusted($sce.HTML, value)`
21104 */
21105
21106 /**
21107 * @ngdoc method
21108 * @name $sce#getTrustedCss
21109 *
21110 * @description
21111 * Shorthand method. `$sce.getTrustedCss(value)` →
21112 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
21113 *
21114 * @param {*} value The value to pass to `$sce.getTrusted`.
21115 * @return {*} The return value of `$sce.getTrusted($sce.CSS, value)`
21116 */
21117
21118 /**
21119 * @ngdoc method
21120 * @name $sce#getTrustedUrl
21121 *
21122 * @description
21123 * Shorthand method. `$sce.getTrustedUrl(value)` →
21124 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
21125 *
21126 * @param {*} value The value to pass to `$sce.getTrusted`.
21127 * @return {*} The return value of `$sce.getTrusted($sce.URL, value)`
21128 */
21129
21130 /**
21131 * @ngdoc method
21132 * @name $sce#getTrustedResourceUrl
21133 *
21134 * @description
21135 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
21136 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
21137 *
21138 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
21139 * @return {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
21140 */
21141
21142 /**
21143 * @ngdoc method
21144 * @name $sce#getTrustedJs
21145 *
21146 * @description
21147 * Shorthand method. `$sce.getTrustedJs(value)` →
21148 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
21149 *
21150 * @param {*} value The value to pass to `$sce.getTrusted`.
21151 * @return {*} The return value of `$sce.getTrusted($sce.JS, value)`
21152 */
21153
21154 /**
21155 * @ngdoc method
21156 * @name $sce#parseAsHtml
21157 *
21158 * @description
21159 * Shorthand method. `$sce.parseAsHtml(expression string)` →
21160 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
21161 *
21162 * @param {string} expression String expression to compile.
21163 * @return {function(context, locals)} A function which represents the compiled expression:
21164 *
21165 * * `context` – `{object}` – an object against which any expressions embedded in the
21166 * strings are evaluated against (typically a scope object).
21167 * * `locals` – `{object=}` – local variables context object, useful for overriding values
21168 * in `context`.
21169 */
21170
21171 /**
21172 * @ngdoc method
21173 * @name $sce#parseAsCss
21174 *
21175 * @description
21176 * Shorthand method. `$sce.parseAsCss(value)` →
21177 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
21178 *
21179 * @param {string} expression String expression to compile.
21180 * @return {function(context, locals)} A function which represents the compiled expression:
21181 *
21182 * * `context` – `{object}` – an object against which any expressions embedded in the
21183 * strings are evaluated against (typically a scope object).
21184 * * `locals` – `{object=}` – local variables context object, useful for overriding values
21185 * in `context`.
21186 */
21187
21188 /**
21189 * @ngdoc method
21190 * @name $sce#parseAsUrl
21191 *
21192 * @description
21193 * Shorthand method. `$sce.parseAsUrl(value)` →
21194 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
21195 *
21196 * @param {string} expression String expression to compile.
21197 * @return {function(context, locals)} A function which represents the compiled expression:
21198 *
21199 * * `context` – `{object}` – an object against which any expressions embedded in the
21200 * strings are evaluated against (typically a scope object).
21201 * * `locals` – `{object=}` – local variables context object, useful for overriding values
21202 * in `context`.
21203 */
21204
21205 /**
21206 * @ngdoc method
21207 * @name $sce#parseAsResourceUrl
21208 *
21209 * @description
21210 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
21211 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
21212 *
21213 * @param {string} expression String expression to compile.
21214 * @return {function(context, locals)} A function which represents the compiled expression:
21215 *
21216 * * `context` – `{object}` – an object against which any expressions embedded in the
21217 * strings are evaluated against (typically a scope object).
21218 * * `locals` – `{object=}` – local variables context object, useful for overriding values
21219 * in `context`.
21220 */
21221
21222 /**
21223 * @ngdoc method
21224 * @name $sce#parseAsJs
21225 *
21226 * @description
21227 * Shorthand method. `$sce.parseAsJs(value)` →
21228 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
21229 *
21230 * @param {string} expression String expression to compile.
21231 * @return {function(context, locals)} A function which represents the compiled expression:
21232 *
21233 * * `context` – `{object}` – an object against which any expressions embedded in the
21234 * strings are evaluated against (typically a scope object).
21235 * * `locals` – `{object=}` – local variables context object, useful for overriding values
21236 * in `context`.
21237 */
21238
21239 // Shorthand delegations.
21240 var parse = sce.parseAs,
21241 getTrusted = sce.getTrusted,
21242 trustAs = sce.trustAs;
21243
21244 forEach(SCE_CONTEXTS, function(enumValue, name) {
21245 var lName = lowercase(name);
21246 sce[snakeToCamel('parse_as_' + lName)] = function(expr) {
21247 return parse(enumValue, expr);
21248 };
21249 sce[snakeToCamel('get_trusted_' + lName)] = function(value) {
21250 return getTrusted(enumValue, value);
21251 };
21252 sce[snakeToCamel('trust_as_' + lName)] = function(value) {
21253 return trustAs(enumValue, value);
21254 };
21255 });
21256
21257 return sce;
21258 }];
21259}
21260
21261/* exported $SnifferProvider */
21262
21263/**
21264 * !!! This is an undocumented "private" service !!!
21265 *
21266 * @name $sniffer
21267 * @requires $window
21268 * @requires $document
21269 * @this
21270 *
21271 * @property {boolean} history Does the browser support html5 history api ?
21272 * @property {boolean} transitions Does the browser support CSS transition events ?
21273 * @property {boolean} animations Does the browser support CSS animation events ?
21274 *
21275 * @description
21276 * This is very simple implementation of testing browser's features.
21277 */
21278function $SnifferProvider() {
21279 this.$get = ['$window', '$document', function($window, $document) {
21280 var eventSupport = {},
21281 // Chrome Packaged Apps are not allowed to access `history.pushState`.
21282 // If not sandboxed, they can be detected by the presence of `chrome.app.runtime`
21283 // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by
21284 // the presence of an extension runtime ID and the absence of other Chrome runtime APIs
21285 // (see https://developer.chrome.com/apps/manifest/sandbox).
21286 // (NW.js apps have access to Chrome APIs, but do support `history`.)
21287 isNw = $window.nw && $window.nw.process,
21288 isChromePackagedApp =
21289 !isNw &&
21290 $window.chrome &&
21291 ($window.chrome.app && $window.chrome.app.runtime ||
21292 !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id),
21293 hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState,
21294 android =
21295 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
21296 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
21297 document = $document[0] || {},
21298 bodyStyle = document.body && document.body.style,
21299 transitions = false,
21300 animations = false;
21301
21302 if (bodyStyle) {
21303 // Support: Android <5, Blackberry Browser 10, default Chrome in Android 4.4.x
21304 // Mentioned browsers need a -webkit- prefix for transitions & animations.
21305 transitions = !!('transition' in bodyStyle || 'webkitTransition' in bodyStyle);
21306 animations = !!('animation' in bodyStyle || 'webkitAnimation' in bodyStyle);
21307 }
21308
21309
21310 return {
21311 // Android has history.pushState, but it does not update location correctly
21312 // so let's not use the history API at all.
21313 // http://code.google.com/p/android/issues/detail?id=17471
21314 // https://github.com/angular/angular.js/issues/904
21315
21316 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
21317 // so let's not use the history API also
21318 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
21319 history: !!(hasHistoryPushState && !(android < 4) && !boxee),
21320 hasEvent: function(event) {
21321 // Support: IE 9-11 only
21322 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
21323 // it. In particular the event is not fired when backspace or delete key are pressed or
21324 // when cut operation is performed.
21325 // IE10+ implements 'input' event but it erroneously fires under various situations,
21326 // e.g. when placeholder changes, or a form is focused.
21327 if (event === 'input' && msie) return false;
21328
21329 if (isUndefined(eventSupport[event])) {
21330 var divElm = document.createElement('div');
21331 eventSupport[event] = 'on' + event in divElm;
21332 }
21333
21334 return eventSupport[event];
21335 },
21336 csp: csp(),
21337 transitions: transitions,
21338 animations: animations,
21339 android: android
21340 };
21341 }];
21342}
21343
21344/**
21345 * ! This is a private undocumented service !
21346 *
21347 * @name $$taskTrackerFactory
21348 * @description
21349 * A function to create `TaskTracker` instances.
21350 *
21351 * A `TaskTracker` can keep track of pending tasks (grouped by type) and can notify interested
21352 * parties when all pending tasks (or tasks of a specific type) have been completed.
21353 *
21354 * @param {$log} log - A logger instance (such as `$log`). Used to log error during callback
21355 * execution.
21356 *
21357 * @this
21358 */
21359function $$TaskTrackerFactoryProvider() {
21360 this.$get = valueFn(function(log) { return new TaskTracker(log); });
21361}
21362
21363function TaskTracker(log) {
21364 var self = this;
21365 var taskCounts = {};
21366 var taskCallbacks = [];
21367
21368 var ALL_TASKS_TYPE = self.ALL_TASKS_TYPE = '$$all$$';
21369 var DEFAULT_TASK_TYPE = self.DEFAULT_TASK_TYPE = '$$default$$';
21370
21371 /**
21372 * Execute the specified function and decrement the appropriate `taskCounts` counter.
21373 * If the counter reaches 0, all corresponding `taskCallbacks` are executed.
21374 *
21375 * @param {Function} fn - The function to execute.
21376 * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task that is being completed.
21377 */
21378 self.completeTask = completeTask;
21379
21380 /**
21381 * Increase the task count for the specified task type (or the default task type if non is
21382 * specified).
21383 *
21384 * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task whose count will be increased.
21385 */
21386 self.incTaskCount = incTaskCount;
21387
21388 /**
21389 * Execute the specified callback when all pending tasks have been completed.
21390 *
21391 * If there are no pending tasks, the callback is executed immediately. You can optionally limit
21392 * the tasks that will be waited for to a specific type, by passing a `taskType`.
21393 *
21394 * @param {function} callback - The function to call when there are no pending tasks.
21395 * @param {string=} [taskType=ALL_TASKS_TYPE] - The type of tasks that will be waited for.
21396 */
21397 self.notifyWhenNoPendingTasks = notifyWhenNoPendingTasks;
21398
21399 function completeTask(fn, taskType) {
21400 taskType = taskType || DEFAULT_TASK_TYPE;
21401
21402 try {
21403 fn();
21404 } finally {
21405 decTaskCount(taskType);
21406
21407 var countForType = taskCounts[taskType];
21408 var countForAll = taskCounts[ALL_TASKS_TYPE];
21409
21410 // If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks.
21411 if (!countForAll || !countForType) {
21412 var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType;
21413 var nextCb;
21414
21415 while ((nextCb = getNextCallback(taskType))) {
21416 try {
21417 nextCb();
21418 } catch (e) {
21419 log.error(e);
21420 }
21421 }
21422 }
21423 }
21424 }
21425
21426 function decTaskCount(taskType) {
21427 taskType = taskType || DEFAULT_TASK_TYPE;
21428 if (taskCounts[taskType]) {
21429 taskCounts[taskType]--;
21430 taskCounts[ALL_TASKS_TYPE]--;
21431 }
21432 }
21433
21434 function getLastCallback() {
21435 var cbInfo = taskCallbacks.pop();
21436 return cbInfo && cbInfo.cb;
21437 }
21438
21439 function getLastCallbackForType(taskType) {
21440 for (var i = taskCallbacks.length - 1; i >= 0; --i) {
21441 var cbInfo = taskCallbacks[i];
21442 if (cbInfo.type === taskType) {
21443 taskCallbacks.splice(i, 1);
21444 return cbInfo.cb;
21445 }
21446 }
21447 }
21448
21449 function incTaskCount(taskType) {
21450 taskType = taskType || DEFAULT_TASK_TYPE;
21451 taskCounts[taskType] = (taskCounts[taskType] || 0) + 1;
21452 taskCounts[ALL_TASKS_TYPE] = (taskCounts[ALL_TASKS_TYPE] || 0) + 1;
21453 }
21454
21455 function notifyWhenNoPendingTasks(callback, taskType) {
21456 taskType = taskType || ALL_TASKS_TYPE;
21457 if (!taskCounts[taskType]) {
21458 callback();
21459 } else {
21460 taskCallbacks.push({type: taskType, cb: callback});
21461 }
21462 }
21463}
21464
21465var $templateRequestMinErr = minErr('$templateRequest');
21466
21467/**
21468 * @ngdoc provider
21469 * @name $templateRequestProvider
21470 * @this
21471 *
21472 * @description
21473 * Used to configure the options passed to the {@link $http} service when making a template request.
21474 *
21475 * For example, it can be used for specifying the "Accept" header that is sent to the server, when
21476 * requesting a template.
21477 */
21478function $TemplateRequestProvider() {
21479
21480 var httpOptions;
21481
21482 /**
21483 * @ngdoc method
21484 * @name $templateRequestProvider#httpOptions
21485 * @description
21486 * The options to be passed to the {@link $http} service when making the request.
21487 * You can use this to override options such as the "Accept" header for template requests.
21488 *
21489 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the
21490 * options if not overridden here.
21491 *
21492 * @param {string=} value new value for the {@link $http} options.
21493 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter.
21494 */
21495 this.httpOptions = function(val) {
21496 if (val) {
21497 httpOptions = val;
21498 return this;
21499 }
21500 return httpOptions;
21501 };
21502
21503 /**
21504 * @ngdoc service
21505 * @name $templateRequest
21506 *
21507 * @description
21508 * The `$templateRequest` service runs security checks then downloads the provided template using
21509 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
21510 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
21511 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
21512 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
21513 * when `tpl` is of type string and `$templateCache` has the matching entry.
21514 *
21515 * If you want to pass custom options to the `$http` service, such as setting the Accept header you
21516 * can configure this via {@link $templateRequestProvider#httpOptions}.
21517 *
21518 * `$templateRequest` is used internally by {@link $compile}, {@link ngRoute.$route}, and directives such
21519 * as {@link ngInclude} to download and cache templates.
21520 *
21521 * 3rd party modules should use `$templateRequest` if their services or directives are loading
21522 * templates.
21523 *
21524 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
21525 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
21526 *
21527 * @return {Promise} a promise for the HTTP response data of the given URL.
21528 *
21529 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
21530 */
21531 this.$get = ['$exceptionHandler', '$templateCache', '$http', '$q', '$sce',
21532 function($exceptionHandler, $templateCache, $http, $q, $sce) {
21533
21534 function handleRequestFn(tpl, ignoreRequestError) {
21535 handleRequestFn.totalPendingRequests++;
21536
21537 // We consider the template cache holds only trusted templates, so
21538 // there's no need to go through adding the template again to the trusted
21539 // resources for keys that already are included in there. This also makes
21540 // AngularJS accept any script directive, no matter its name. However, we
21541 // still need to unwrap trusted types.
21542 if (!isString(tpl) || isUndefined($templateCache.get(tpl))) {
21543 tpl = $sce.getTrustedResourceUrl(tpl);
21544 }
21545
21546 var transformResponse = $http.defaults && $http.defaults.transformResponse;
21547
21548 if (isArray(transformResponse)) {
21549 transformResponse = transformResponse.filter(function(transformer) {
21550 return transformer !== defaultHttpResponseTransform;
21551 });
21552 } else if (transformResponse === defaultHttpResponseTransform) {
21553 transformResponse = null;
21554 }
21555
21556 return $http.get(tpl, extend({
21557 cache: $templateCache,
21558 transformResponse: transformResponse
21559 }, httpOptions))
21560 .finally(function() {
21561 handleRequestFn.totalPendingRequests--;
21562 })
21563 .then(function(response) {
21564 return $templateCache.put(tpl, response.data);
21565 }, handleError);
21566
21567 function handleError(resp) {
21568 if (!ignoreRequestError) {
21569 resp = $templateRequestMinErr('tpload',
21570 'Failed to load template: {0} (HTTP status: {1} {2})',
21571 tpl, resp.status, resp.statusText);
21572
21573 $exceptionHandler(resp);
21574 }
21575
21576 return $q.reject(resp);
21577 }
21578 }
21579
21580 handleRequestFn.totalPendingRequests = 0;
21581
21582 return handleRequestFn;
21583 }
21584 ];
21585}
21586
21587/** @this */
21588function $$TestabilityProvider() {
21589 this.$get = ['$rootScope', '$browser', '$location',
21590 function($rootScope, $browser, $location) {
21591
21592 /**
21593 * @name $testability
21594 *
21595 * @description
21596 * The private $$testability service provides a collection of methods for use when debugging
21597 * or by automated test and debugging tools.
21598 */
21599 var testability = {};
21600
21601 /**
21602 * @name $$testability#findBindings
21603 *
21604 * @description
21605 * Returns an array of elements that are bound (via ng-bind or {{}})
21606 * to expressions matching the input.
21607 *
21608 * @param {Element} element The element root to search from.
21609 * @param {string} expression The binding expression to match.
21610 * @param {boolean} opt_exactMatch If true, only returns exact matches
21611 * for the expression. Filters and whitespace are ignored.
21612 */
21613 testability.findBindings = function(element, expression, opt_exactMatch) {
21614 var bindings = element.getElementsByClassName('ng-binding');
21615 var matches = [];
21616 forEach(bindings, function(binding) {
21617 var dataBinding = angular.element(binding).data('$binding');
21618 if (dataBinding) {
21619 forEach(dataBinding, function(bindingName) {
21620 if (opt_exactMatch) {
21621 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
21622 if (matcher.test(bindingName)) {
21623 matches.push(binding);
21624 }
21625 } else {
21626 if (bindingName.indexOf(expression) !== -1) {
21627 matches.push(binding);
21628 }
21629 }
21630 });
21631 }
21632 });
21633 return matches;
21634 };
21635
21636 /**
21637 * @name $$testability#findModels
21638 *
21639 * @description
21640 * Returns an array of elements that are two-way found via ng-model to
21641 * expressions matching the input.
21642 *
21643 * @param {Element} element The element root to search from.
21644 * @param {string} expression The model expression to match.
21645 * @param {boolean} opt_exactMatch If true, only returns exact matches
21646 * for the expression.
21647 */
21648 testability.findModels = function(element, expression, opt_exactMatch) {
21649 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
21650 for (var p = 0; p < prefixes.length; ++p) {
21651 var attributeEquals = opt_exactMatch ? '=' : '*=';
21652 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
21653 var elements = element.querySelectorAll(selector);
21654 if (elements.length) {
21655 return elements;
21656 }
21657 }
21658 };
21659
21660 /**
21661 * @name $$testability#getLocation
21662 *
21663 * @description
21664 * Shortcut for getting the location in a browser agnostic way. Returns
21665 * the path, search, and hash. (e.g. /path?a=b#hash)
21666 */
21667 testability.getLocation = function() {
21668 return $location.url();
21669 };
21670
21671 /**
21672 * @name $$testability#setLocation
21673 *
21674 * @description
21675 * Shortcut for navigating to a location without doing a full page reload.
21676 *
21677 * @param {string} url The location url (path, search and hash,
21678 * e.g. /path?a=b#hash) to go to.
21679 */
21680 testability.setLocation = function(url) {
21681 if (url !== $location.url()) {
21682 $location.url(url);
21683 $rootScope.$digest();
21684 }
21685 };
21686
21687 /**
21688 * @name $$testability#whenStable
21689 *
21690 * @description
21691 * Calls the callback when all pending tasks are completed.
21692 *
21693 * Types of tasks waited for include:
21694 * - Pending timeouts (via {@link $timeout}).
21695 * - Pending HTTP requests (via {@link $http}).
21696 * - In-progress route transitions (via {@link $route}).
21697 * - Pending tasks scheduled via {@link $rootScope#$applyAsync}.
21698 * - Pending tasks scheduled via {@link $rootScope#$evalAsync}.
21699 * These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
21700 *
21701 * @param {function} callback
21702 */
21703 testability.whenStable = function(callback) {
21704 $browser.notifyWhenNoOutstandingRequests(callback);
21705 };
21706
21707 return testability;
21708 }];
21709}
21710
21711var $timeoutMinErr = minErr('$timeout');
21712
21713/** @this */
21714function $TimeoutProvider() {
21715 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
21716 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
21717
21718 var deferreds = {};
21719
21720
21721 /**
21722 * @ngdoc service
21723 * @name $timeout
21724 *
21725 * @description
21726 * AngularJS's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
21727 * block and delegates any exceptions to
21728 * {@link ng.$exceptionHandler $exceptionHandler} service.
21729 *
21730 * The return value of calling `$timeout` is a promise, which will be resolved when
21731 * the delay has passed and the timeout function, if provided, is executed.
21732 *
21733 * To cancel a timeout request, call `$timeout.cancel(promise)`.
21734 *
21735 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
21736 * synchronously flush the queue of deferred functions.
21737 *
21738 * If you only want a promise that will be resolved after some specified delay
21739 * then you can call `$timeout` without the `fn` function.
21740 *
21741 * @param {function()=} fn A function, whose execution should be delayed.
21742 * @param {number=} [delay=0] Delay in milliseconds.
21743 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
21744 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
21745 * @param {...*=} Pass additional parameters to the executed function.
21746 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise
21747 * will be resolved with the return value of the `fn` function.
21748 *
21749 */
21750 function timeout(fn, delay, invokeApply) {
21751 if (!isFunction(fn)) {
21752 invokeApply = delay;
21753 delay = fn;
21754 fn = noop;
21755 }
21756
21757 var args = sliceArgs(arguments, 3),
21758 skipApply = (isDefined(invokeApply) && !invokeApply),
21759 deferred = (skipApply ? $$q : $q).defer(),
21760 promise = deferred.promise,
21761 timeoutId;
21762
21763 timeoutId = $browser.defer(function() {
21764 try {
21765 deferred.resolve(fn.apply(null, args));
21766 } catch (e) {
21767 deferred.reject(e);
21768 $exceptionHandler(e);
21769 } finally {
21770 delete deferreds[promise.$$timeoutId];
21771 }
21772
21773 if (!skipApply) $rootScope.$apply();
21774 }, delay, '$timeout');
21775
21776 promise.$$timeoutId = timeoutId;
21777 deferreds[timeoutId] = deferred;
21778
21779 return promise;
21780 }
21781
21782
21783 /**
21784 * @ngdoc method
21785 * @name $timeout#cancel
21786 *
21787 * @description
21788 * Cancels a task associated with the `promise`. As a result of this, the promise will be
21789 * resolved with a rejection.
21790 *
21791 * @param {Promise=} promise Promise returned by the `$timeout` function.
21792 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
21793 * canceled.
21794 */
21795 timeout.cancel = function(promise) {
21796 if (!promise) return false;
21797
21798 if (!promise.hasOwnProperty('$$timeoutId')) {
21799 throw $timeoutMinErr('badprom',
21800 '`$timeout.cancel()` called with a promise that was not generated by `$timeout()`.');
21801 }
21802
21803 if (!deferreds.hasOwnProperty(promise.$$timeoutId)) return false;
21804
21805 var id = promise.$$timeoutId;
21806 var deferred = deferreds[id];
21807
21808 // Timeout cancels should not report an unhandled promise.
21809 markQExceptionHandled(deferred.promise);
21810 deferred.reject('canceled');
21811 delete deferreds[id];
21812
21813 return $browser.defer.cancel(id);
21814 };
21815
21816 return timeout;
21817 }];
21818}
21819
21820// NOTE: The usage of window and document instead of $window and $document here is
21821// deliberate. This service depends on the specific behavior of anchor nodes created by the
21822// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
21823// cause us to break tests. In addition, when the browser resolves a URL for XHR, it
21824// doesn't know about mocked locations and resolves URLs to the real document - which is
21825// exactly the behavior needed here. There is little value is mocking these out for this
21826// service.
21827var urlParsingNode = window.document.createElement('a');
21828var originUrl = urlResolve(window.location.href);
21829var baseUrlParsingNode;
21830
21831urlParsingNode.href = 'http://[::1]';
21832
21833// Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview)
21834// IE/Edge don't wrap IPv6 addresses' hostnames in square brackets
21835// when parsed out of an anchor element.
21836var ipv6InBrackets = urlParsingNode.hostname === '[::1]';
21837
21838/**
21839 *
21840 * Implementation Notes for non-IE browsers
21841 * ----------------------------------------
21842 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
21843 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
21844 * URL will be resolved into an absolute URL in the context of the application document.
21845 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
21846 * properties are all populated to reflect the normalized URL. This approach has wide
21847 * compatibility - Safari 1+, Mozilla 1+ etc. See
21848 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
21849 *
21850 * Implementation Notes for IE
21851 * ---------------------------
21852 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
21853 * browsers. However, the parsed components will not be set if the URL assigned did not specify
21854 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
21855 * work around that by performing the parsing in a 2nd step by taking a previously normalized
21856 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
21857 * properties such as protocol, hostname, port, etc.
21858 *
21859 * References:
21860 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
21861 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
21862 * http://url.spec.whatwg.org/#urlutils
21863 * https://github.com/angular/angular.js/pull/2902
21864 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
21865 *
21866 * @kind function
21867 * @param {string|object} url The URL to be parsed. If `url` is not a string, it will be returned
21868 * unchanged.
21869 * @description Normalizes and parses a URL.
21870 * @returns {object} Returns the normalized URL as a dictionary.
21871 *
21872 * | member name | Description |
21873 * |---------------|------------------------------------------------------------------------|
21874 * | href | A normalized version of the provided URL if it was not an absolute URL |
21875 * | protocol | The protocol without the trailing colon |
21876 * | host | The host and port (if the port is non-default) of the normalizedUrl |
21877 * | search | The search params, minus the question mark |
21878 * | hash | The hash string, minus the hash symbol |
21879 * | hostname | The hostname |
21880 * | port | The port, without ":" |
21881 * | pathname | The pathname, beginning with "/" |
21882 *
21883 */
21884function urlResolve(url) {
21885 if (!isString(url)) return url;
21886
21887 var href = url;
21888
21889 // Support: IE 9-11 only
21890 if (msie) {
21891 // Normalize before parse. Refer Implementation Notes on why this is
21892 // done in two steps on IE.
21893 urlParsingNode.setAttribute('href', href);
21894 href = urlParsingNode.href;
21895 }
21896
21897 urlParsingNode.setAttribute('href', href);
21898
21899 var hostname = urlParsingNode.hostname;
21900
21901 if (!ipv6InBrackets && hostname.indexOf(':') > -1) {
21902 hostname = '[' + hostname + ']';
21903 }
21904
21905 return {
21906 href: urlParsingNode.href,
21907 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
21908 host: urlParsingNode.host,
21909 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
21910 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
21911 hostname: hostname,
21912 port: urlParsingNode.port,
21913 pathname: (urlParsingNode.pathname.charAt(0) === '/')
21914 ? urlParsingNode.pathname
21915 : '/' + urlParsingNode.pathname
21916 };
21917}
21918
21919/**
21920 * Parse a request URL and determine whether this is a same-origin request as the application
21921 * document.
21922 *
21923 * @param {string|object} requestUrl The url of the request as a string that will be resolved
21924 * or a parsed URL object.
21925 * @returns {boolean} Whether the request is for the same origin as the application document.
21926 */
21927function urlIsSameOrigin(requestUrl) {
21928 return urlsAreSameOrigin(requestUrl, originUrl);
21929}
21930
21931/**
21932 * Parse a request URL and determine whether it is same-origin as the current document base URL.
21933 *
21934 * Note: The base URL is usually the same as the document location (`location.href`) but can
21935 * be overriden by using the `<base>` tag.
21936 *
21937 * @param {string|object} requestUrl The url of the request as a string that will be resolved
21938 * or a parsed URL object.
21939 * @returns {boolean} Whether the URL is same-origin as the document base URL.
21940 */
21941function urlIsSameOriginAsBaseUrl(requestUrl) {
21942 return urlsAreSameOrigin(requestUrl, getBaseUrl());
21943}
21944
21945/**
21946 * Create a function that can check a URL's origin against a list of allowed/trusted origins.
21947 * The current location's origin is implicitly trusted.
21948 *
21949 * @param {string[]} trustedOriginUrls - A list of URLs (strings), whose origins are trusted.
21950 *
21951 * @returns {Function} - A function that receives a URL (string or parsed URL object) and returns
21952 * whether it is of an allowed origin.
21953 */
21954function urlIsAllowedOriginFactory(trustedOriginUrls) {
21955 var parsedAllowedOriginUrls = [originUrl].concat(trustedOriginUrls.map(urlResolve));
21956
21957 /**
21958 * Check whether the specified URL (string or parsed URL object) has an origin that is allowed
21959 * based on a list of trusted-origin URLs. The current location's origin is implicitly
21960 * trusted.
21961 *
21962 * @param {string|Object} requestUrl - The URL to be checked (provided as a string that will be
21963 * resolved or a parsed URL object).
21964 *
21965 * @returns {boolean} - Whether the specified URL is of an allowed origin.
21966 */
21967 return function urlIsAllowedOrigin(requestUrl) {
21968 var parsedUrl = urlResolve(requestUrl);
21969 return parsedAllowedOriginUrls.some(urlsAreSameOrigin.bind(null, parsedUrl));
21970 };
21971}
21972
21973/**
21974 * Determine if two URLs share the same origin.
21975 *
21976 * @param {string|Object} url1 - First URL to compare as a string or a normalized URL in the form of
21977 * a dictionary object returned by `urlResolve()`.
21978 * @param {string|object} url2 - Second URL to compare as a string or a normalized URL in the form
21979 * of a dictionary object returned by `urlResolve()`.
21980 *
21981 * @returns {boolean} - True if both URLs have the same origin, and false otherwise.
21982 */
21983function urlsAreSameOrigin(url1, url2) {
21984 url1 = urlResolve(url1);
21985 url2 = urlResolve(url2);
21986
21987 return (url1.protocol === url2.protocol &&
21988 url1.host === url2.host);
21989}
21990
21991/**
21992 * Returns the current document base URL.
21993 * @returns {string}
21994 */
21995function getBaseUrl() {
21996 if (window.document.baseURI) {
21997 return window.document.baseURI;
21998 }
21999
22000 // `document.baseURI` is available everywhere except IE
22001 if (!baseUrlParsingNode) {
22002 baseUrlParsingNode = window.document.createElement('a');
22003 baseUrlParsingNode.href = '.';
22004
22005 // Work-around for IE bug described in Implementation Notes. The fix in `urlResolve()` is not
22006 // suitable here because we need to track changes to the base URL.
22007 baseUrlParsingNode = baseUrlParsingNode.cloneNode(false);
22008 }
22009 return baseUrlParsingNode.href;
22010}
22011
22012/**
22013 * @ngdoc service
22014 * @name $window
22015 * @this
22016 *
22017 * @description
22018 * A reference to the browser's `window` object. While `window`
22019 * is globally available in JavaScript, it causes testability problems, because
22020 * it is a global variable. In AngularJS we always refer to it through the
22021 * `$window` service, so it may be overridden, removed or mocked for testing.
22022 *
22023 * Expressions, like the one defined for the `ngClick` directive in the example
22024 * below, are evaluated with respect to the current scope. Therefore, there is
22025 * no risk of inadvertently coding in a dependency on a global value in such an
22026 * expression.
22027 *
22028 * @example
22029 <example module="windowExample" name="window-service">
22030 <file name="index.html">
22031 <script>
22032 angular.module('windowExample', [])
22033 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
22034 $scope.greeting = 'Hello, World!';
22035 $scope.doGreeting = function(greeting) {
22036 $window.alert(greeting);
22037 };
22038 }]);
22039 </script>
22040 <div ng-controller="ExampleController">
22041 <input type="text" ng-model="greeting" aria-label="greeting" />
22042 <button ng-click="doGreeting(greeting)">ALERT</button>
22043 </div>
22044 </file>
22045 <file name="protractor.js" type="protractor">
22046 it('should display the greeting in the input box', function() {
22047 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
22048 // If we click the button it will block the test runner
22049 // element(':button').click();
22050 });
22051 </file>
22052 </example>
22053 */
22054function $WindowProvider() {
22055 this.$get = valueFn(window);
22056}
22057
22058/**
22059 * @name $$cookieReader
22060 * @requires $document
22061 *
22062 * @description
22063 * This is a private service for reading cookies used by $http and ngCookies
22064 *
22065 * @return {Object} a key/value map of the current cookies
22066 */
22067function $$CookieReader($document) {
22068 var rawDocument = $document[0] || {};
22069 var lastCookies = {};
22070 var lastCookieString = '';
22071
22072 function safeGetCookie(rawDocument) {
22073 try {
22074 return rawDocument.cookie || '';
22075 } catch (e) {
22076 return '';
22077 }
22078 }
22079
22080 function safeDecodeURIComponent(str) {
22081 try {
22082 return decodeURIComponent(str);
22083 } catch (e) {
22084 return str;
22085 }
22086 }
22087
22088 return function() {
22089 var cookieArray, cookie, i, index, name;
22090 var currentCookieString = safeGetCookie(rawDocument);
22091
22092 if (currentCookieString !== lastCookieString) {
22093 lastCookieString = currentCookieString;
22094 cookieArray = lastCookieString.split('; ');
22095 lastCookies = {};
22096
22097 for (i = 0; i < cookieArray.length; i++) {
22098 cookie = cookieArray[i];
22099 index = cookie.indexOf('=');
22100 if (index > 0) { //ignore nameless cookies
22101 name = safeDecodeURIComponent(cookie.substring(0, index));
22102 // the first value that is seen for a cookie is the most
22103 // specific one. values for the same cookie name that
22104 // follow are for less specific paths.
22105 if (isUndefined(lastCookies[name])) {
22106 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
22107 }
22108 }
22109 }
22110 }
22111 return lastCookies;
22112 };
22113}
22114
22115$$CookieReader.$inject = ['$document'];
22116
22117/** @this */
22118function $$CookieReaderProvider() {
22119 this.$get = $$CookieReader;
22120}
22121
22122/* global currencyFilter: true,
22123 dateFilter: true,
22124 filterFilter: true,
22125 jsonFilter: true,
22126 limitToFilter: true,
22127 lowercaseFilter: true,
22128 numberFilter: true,
22129 orderByFilter: true,
22130 uppercaseFilter: true,
22131 */
22132
22133/**
22134 * @ngdoc provider
22135 * @name $filterProvider
22136 * @description
22137 *
22138 * Filters are just functions which transform input to an output. However filters need to be
22139 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
22140 * annotated with dependencies and is responsible for creating a filter function.
22141 *
22142 * <div class="alert alert-warning">
22143 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`.
22144 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
22145 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
22146 * (`myapp_subsection_filterx`).
22147 * </div>
22148 *
22149 * ```js
22150 * // Filter registration
22151 * function MyModule($provide, $filterProvider) {
22152 * // create a service to demonstrate injection (not always needed)
22153 * $provide.value('greet', function(name){
22154 * return 'Hello ' + name + '!';
22155 * });
22156 *
22157 * // register a filter factory which uses the
22158 * // greet service to demonstrate DI.
22159 * $filterProvider.register('greet', function(greet){
22160 * // return the filter function which uses the greet service
22161 * // to generate salutation
22162 * return function(text) {
22163 * // filters need to be forgiving so check input validity
22164 * return text && greet(text) || text;
22165 * };
22166 * });
22167 * }
22168 * ```
22169 *
22170 * The filter function is registered with the `$injector` under the filter name suffix with
22171 * `Filter`.
22172 *
22173 * ```js
22174 * it('should be the same instance', inject(
22175 * function($filterProvider) {
22176 * $filterProvider.register('reverse', function(){
22177 * return ...;
22178 * });
22179 * },
22180 * function($filter, reverseFilter) {
22181 * expect($filter('reverse')).toBe(reverseFilter);
22182 * });
22183 * ```
22184 *
22185 *
22186 * For more information about how AngularJS filters work, and how to create your own filters, see
22187 * {@link guide/filter Filters} in the AngularJS Developer Guide.
22188 */
22189
22190/**
22191 * @ngdoc service
22192 * @name $filter
22193 * @kind function
22194 * @description
22195 * Filters are used for formatting data displayed to the user.
22196 *
22197 * They can be used in view templates, controllers or services. AngularJS comes
22198 * with a collection of [built-in filters](api/ng/filter), but it is easy to
22199 * define your own as well.
22200 *
22201 * The general syntax in templates is as follows:
22202 *
22203 * ```html
22204 * {{ expression [| filter_name[:parameter_value] ... ] }}
22205 * ```
22206 *
22207 * @param {String} name Name of the filter function to retrieve
22208 * @return {Function} the filter function
22209 * @example
22210 <example name="$filter" module="filterExample">
22211 <file name="index.html">
22212 <div ng-controller="MainCtrl">
22213 <h3>{{ originalText }}</h3>
22214 <h3>{{ filteredText }}</h3>
22215 </div>
22216 </file>
22217
22218 <file name="script.js">
22219 angular.module('filterExample', [])
22220 .controller('MainCtrl', function($scope, $filter) {
22221 $scope.originalText = 'hello';
22222 $scope.filteredText = $filter('uppercase')($scope.originalText);
22223 });
22224 </file>
22225 </example>
22226 */
22227$FilterProvider.$inject = ['$provide'];
22228/** @this */
22229function $FilterProvider($provide) {
22230 var suffix = 'Filter';
22231
22232 /**
22233 * @ngdoc method
22234 * @name $filterProvider#register
22235 * @param {string|Object} name Name of the filter function, or an object map of filters where
22236 * the keys are the filter names and the values are the filter factories.
22237 *
22238 * <div class="alert alert-warning">
22239 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`.
22240 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
22241 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
22242 * (`myapp_subsection_filterx`).
22243 * </div>
22244 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
22245 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
22246 * of the registered filter instances.
22247 */
22248 function register(name, factory) {
22249 if (isObject(name)) {
22250 var filters = {};
22251 forEach(name, function(filter, key) {
22252 filters[key] = register(key, filter);
22253 });
22254 return filters;
22255 } else {
22256 return $provide.factory(name + suffix, factory);
22257 }
22258 }
22259 this.register = register;
22260
22261 this.$get = ['$injector', function($injector) {
22262 return function(name) {
22263 return $injector.get(name + suffix);
22264 };
22265 }];
22266
22267 ////////////////////////////////////////
22268
22269 /* global
22270 currencyFilter: false,
22271 dateFilter: false,
22272 filterFilter: false,
22273 jsonFilter: false,
22274 limitToFilter: false,
22275 lowercaseFilter: false,
22276 numberFilter: false,
22277 orderByFilter: false,
22278 uppercaseFilter: false
22279 */
22280
22281 register('currency', currencyFilter);
22282 register('date', dateFilter);
22283 register('filter', filterFilter);
22284 register('json', jsonFilter);
22285 register('limitTo', limitToFilter);
22286 register('lowercase', lowercaseFilter);
22287 register('number', numberFilter);
22288 register('orderBy', orderByFilter);
22289 register('uppercase', uppercaseFilter);
22290}
22291
22292/**
22293 * @ngdoc filter
22294 * @name filter
22295 * @kind function
22296 *
22297 * @description
22298 * Selects a subset of items from `array` and returns it as a new array.
22299 *
22300 * @param {Array} array The source array.
22301 * <div class="alert alert-info">
22302 * **Note**: If the array contains objects that reference themselves, filtering is not possible.
22303 * </div>
22304 * @param {string|Object|function()} expression The predicate to be used for selecting items from
22305 * `array`.
22306 *
22307 * Can be one of:
22308 *
22309 * - `string`: The string is used for matching against the contents of the `array`. All strings or
22310 * objects with string properties in `array` that match this string will be returned. This also
22311 * applies to nested object properties.
22312 * The predicate can be negated by prefixing the string with `!`.
22313 *
22314 * - `Object`: A pattern object can be used to filter specific properties on objects contained
22315 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
22316 * which have property `name` containing "M" and property `phone` containing "1". A special
22317 * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match
22318 * against any property of the object or its nested object properties. That's equivalent to the
22319 * simple substring match with a `string` as described above. The special property name can be
22320 * overwritten, using the `anyPropertyKey` parameter.
22321 * The predicate can be negated by prefixing the string with `!`.
22322 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
22323 * not containing "M".
22324 *
22325 * Note that a named property will match properties on the same level only, while the special
22326 * `$` property will match properties on the same level or deeper. E.g. an array item like
22327 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
22328 * **will** be matched by `{$: 'John'}`.
22329 *
22330 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
22331 * The function is called for each element of the array, with the element, its index, and
22332 * the entire array itself as arguments.
22333 *
22334 * The final result is an array of those elements that the predicate returned true for.
22335 *
22336 * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in
22337 * determining if values retrieved using `expression` (when it is not a function) should be
22338 * considered a match based on the expected value (from the filter expression) and actual
22339 * value (from the object in the array).
22340 *
22341 * Can be one of:
22342 *
22343 * - `function(actual, expected)`:
22344 * The function will be given the object value and the predicate value to compare and
22345 * should return true if both values should be considered equal.
22346 *
22347 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
22348 * This is essentially strict comparison of expected and actual.
22349 *
22350 * - `false`: A short hand for a function which will look for a substring match in a case
22351 * insensitive way. Primitive values are converted to strings. Objects are not compared against
22352 * primitives, unless they have a custom `toString` method (e.g. `Date` objects).
22353 *
22354 *
22355 * Defaults to `false`.
22356 *
22357 * @param {string} [anyPropertyKey] The special property name that matches against any property.
22358 * By default `$`.
22359 *
22360 * @example
22361 <example name="filter-filter">
22362 <file name="index.html">
22363 <div ng-init="friends = [{name:'John', phone:'555-1276'},
22364 {name:'Mary', phone:'800-BIG-MARY'},
22365 {name:'Mike', phone:'555-4321'},
22366 {name:'Adam', phone:'555-5678'},
22367 {name:'Julie', phone:'555-8765'},
22368 {name:'Juliette', phone:'555-5678'}]"></div>
22369
22370 <label>Search: <input ng-model="searchText"></label>
22371 <table id="searchTextResults">
22372 <tr><th>Name</th><th>Phone</th></tr>
22373 <tr ng-repeat="friend in friends | filter:searchText">
22374 <td>{{friend.name}}</td>
22375 <td>{{friend.phone}}</td>
22376 </tr>
22377 </table>
22378 <hr>
22379 <label>Any: <input ng-model="search.$"></label> <br>
22380 <label>Name only <input ng-model="search.name"></label><br>
22381 <label>Phone only <input ng-model="search.phone"></label><br>
22382 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
22383 <table id="searchObjResults">
22384 <tr><th>Name</th><th>Phone</th></tr>
22385 <tr ng-repeat="friendObj in friends | filter:search:strict">
22386 <td>{{friendObj.name}}</td>
22387 <td>{{friendObj.phone}}</td>
22388 </tr>
22389 </table>
22390 </file>
22391 <file name="protractor.js" type="protractor">
22392 var expectFriendNames = function(expectedNames, key) {
22393 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
22394 arr.forEach(function(wd, i) {
22395 expect(wd.getText()).toMatch(expectedNames[i]);
22396 });
22397 });
22398 };
22399
22400 it('should search across all fields when filtering with a string', function() {
22401 var searchText = element(by.model('searchText'));
22402 searchText.clear();
22403 searchText.sendKeys('m');
22404 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
22405
22406 searchText.clear();
22407 searchText.sendKeys('76');
22408 expectFriendNames(['John', 'Julie'], 'friend');
22409 });
22410
22411 it('should search in specific fields when filtering with a predicate object', function() {
22412 var searchAny = element(by.model('search.$'));
22413 searchAny.clear();
22414 searchAny.sendKeys('i');
22415 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
22416 });
22417 it('should use a equal comparison when comparator is true', function() {
22418 var searchName = element(by.model('search.name'));
22419 var strict = element(by.model('strict'));
22420 searchName.clear();
22421 searchName.sendKeys('Julie');
22422 strict.click();
22423 expectFriendNames(['Julie'], 'friendObj');
22424 });
22425 </file>
22426 </example>
22427 */
22428
22429function filterFilter() {
22430 return function(array, expression, comparator, anyPropertyKey) {
22431 if (!isArrayLike(array)) {
22432 if (array == null) {
22433 return array;
22434 } else {
22435 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
22436 }
22437 }
22438
22439 anyPropertyKey = anyPropertyKey || '$';
22440 var expressionType = getTypeForFilter(expression);
22441 var predicateFn;
22442 var matchAgainstAnyProp;
22443
22444 switch (expressionType) {
22445 case 'function':
22446 predicateFn = expression;
22447 break;
22448 case 'boolean':
22449 case 'null':
22450 case 'number':
22451 case 'string':
22452 matchAgainstAnyProp = true;
22453 // falls through
22454 case 'object':
22455 predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp);
22456 break;
22457 default:
22458 return array;
22459 }
22460
22461 return Array.prototype.filter.call(array, predicateFn);
22462 };
22463}
22464
22465// Helper functions for `filterFilter`
22466function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) {
22467 var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression);
22468 var predicateFn;
22469
22470 if (comparator === true) {
22471 comparator = equals;
22472 } else if (!isFunction(comparator)) {
22473 comparator = function(actual, expected) {
22474 if (isUndefined(actual)) {
22475 // No substring matching against `undefined`
22476 return false;
22477 }
22478 if ((actual === null) || (expected === null)) {
22479 // No substring matching against `null`; only match against `null`
22480 return actual === expected;
22481 }
22482 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
22483 // Should not compare primitives against objects, unless they have custom `toString` method
22484 return false;
22485 }
22486
22487 actual = lowercase('' + actual);
22488 expected = lowercase('' + expected);
22489 return actual.indexOf(expected) !== -1;
22490 };
22491 }
22492
22493 predicateFn = function(item) {
22494 if (shouldMatchPrimitives && !isObject(item)) {
22495 return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false);
22496 }
22497 return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp);
22498 };
22499
22500 return predicateFn;
22501}
22502
22503function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) {
22504 var actualType = getTypeForFilter(actual);
22505 var expectedType = getTypeForFilter(expected);
22506
22507 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
22508 return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp);
22509 } else if (isArray(actual)) {
22510 // In case `actual` is an array, consider it a match
22511 // if ANY of it's items matches `expected`
22512 return actual.some(function(item) {
22513 return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp);
22514 });
22515 }
22516
22517 switch (actualType) {
22518 case 'object':
22519 var key;
22520 if (matchAgainstAnyProp) {
22521 for (key in actual) {
22522 // Under certain, rare, circumstances, key may not be a string and `charAt` will be undefined
22523 // See: https://github.com/angular/angular.js/issues/15644
22524 if (key.charAt && (key.charAt(0) !== '$') &&
22525 deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) {
22526 return true;
22527 }
22528 }
22529 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false);
22530 } else if (expectedType === 'object') {
22531 for (key in expected) {
22532 var expectedVal = expected[key];
22533 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
22534 continue;
22535 }
22536
22537 var matchAnyProperty = key === anyPropertyKey;
22538 var actualVal = matchAnyProperty ? actual : actual[key];
22539 if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) {
22540 return false;
22541 }
22542 }
22543 return true;
22544 } else {
22545 return comparator(actual, expected);
22546 }
22547 case 'function':
22548 return false;
22549 default:
22550 return comparator(actual, expected);
22551 }
22552}
22553
22554// Used for easily differentiating between `null` and actual `object`
22555function getTypeForFilter(val) {
22556 return (val === null) ? 'null' : typeof val;
22557}
22558
22559var MAX_DIGITS = 22;
22560var DECIMAL_SEP = '.';
22561var ZERO_CHAR = '0';
22562
22563/**
22564 * @ngdoc filter
22565 * @name currency
22566 * @kind function
22567 *
22568 * @description
22569 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
22570 * symbol for current locale is used.
22571 *
22572 * @param {number} amount Input to filter.
22573 * @param {string=} symbol Currency symbol or identifier to be displayed.
22574 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
22575 * @returns {string} Formatted number.
22576 *
22577 *
22578 * @example
22579 <example module="currencyExample" name="currency-filter">
22580 <file name="index.html">
22581 <script>
22582 angular.module('currencyExample', [])
22583 .controller('ExampleController', ['$scope', function($scope) {
22584 $scope.amount = 1234.56;
22585 }]);
22586 </script>
22587 <div ng-controller="ExampleController">
22588 <input type="number" ng-model="amount" aria-label="amount"> <br>
22589 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
22590 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span><br>
22591 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
22592 </div>
22593 </file>
22594 <file name="protractor.js" type="protractor">
22595 it('should init with 1234.56', function() {
22596 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
22597 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
22598 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
22599 });
22600 it('should update', function() {
22601 if (browser.params.browser === 'safari') {
22602 // Safari does not understand the minus key. See
22603 // https://github.com/angular/protractor/issues/481
22604 return;
22605 }
22606 element(by.model('amount')).clear();
22607 element(by.model('amount')).sendKeys('-1234');
22608 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
22609 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
22610 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
22611 });
22612 </file>
22613 </example>
22614 */
22615currencyFilter.$inject = ['$locale'];
22616function currencyFilter($locale) {
22617 var formats = $locale.NUMBER_FORMATS;
22618 return function(amount, currencySymbol, fractionSize) {
22619 if (isUndefined(currencySymbol)) {
22620 currencySymbol = formats.CURRENCY_SYM;
22621 }
22622
22623 if (isUndefined(fractionSize)) {
22624 fractionSize = formats.PATTERNS[1].maxFrac;
22625 }
22626
22627 // If the currency symbol is empty, trim whitespace around the symbol
22628 var currencySymbolRe = !currencySymbol ? /\s*\u00A4\s*/g : /\u00A4/g;
22629
22630 // if null or undefined pass it through
22631 return (amount == null)
22632 ? amount
22633 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
22634 replace(currencySymbolRe, currencySymbol);
22635 };
22636}
22637
22638/**
22639 * @ngdoc filter
22640 * @name number
22641 * @kind function
22642 *
22643 * @description
22644 * Formats a number as text.
22645 *
22646 * If the input is null or undefined, it will just be returned.
22647 * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively.
22648 * If the input is not a number an empty string is returned.
22649 *
22650 *
22651 * @param {number|string} number Number to format.
22652 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
22653 * If this is not provided then the fraction size is computed from the current locale's number
22654 * formatting pattern. In the case of the default locale, it will be 3.
22655 * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current
22656 * locale (e.g., in the en_US locale it will have "." as the decimal separator and
22657 * include "," group separators after each third digit).
22658 *
22659 * @example
22660 <example module="numberFilterExample" name="number-filter">
22661 <file name="index.html">
22662 <script>
22663 angular.module('numberFilterExample', [])
22664 .controller('ExampleController', ['$scope', function($scope) {
22665 $scope.val = 1234.56789;
22666 }]);
22667 </script>
22668 <div ng-controller="ExampleController">
22669 <label>Enter number: <input ng-model='val'></label><br>
22670 Default formatting: <span id='number-default'>{{val | number}}</span><br>
22671 No fractions: <span>{{val | number:0}}</span><br>
22672 Negative number: <span>{{-val | number:4}}</span>
22673 </div>
22674 </file>
22675 <file name="protractor.js" type="protractor">
22676 it('should format numbers', function() {
22677 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
22678 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
22679 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
22680 });
22681
22682 it('should update', function() {
22683 element(by.model('val')).clear();
22684 element(by.model('val')).sendKeys('3374.333');
22685 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
22686 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
22687 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
22688 });
22689 </file>
22690 </example>
22691 */
22692numberFilter.$inject = ['$locale'];
22693function numberFilter($locale) {
22694 var formats = $locale.NUMBER_FORMATS;
22695 return function(number, fractionSize) {
22696
22697 // if null or undefined pass it through
22698 return (number == null)
22699 ? number
22700 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
22701 fractionSize);
22702 };
22703}
22704
22705/**
22706 * Parse a number (as a string) into three components that can be used
22707 * for formatting the number.
22708 *
22709 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/)
22710 *
22711 * @param {string} numStr The number to parse
22712 * @return {object} An object describing this number, containing the following keys:
22713 * - d : an array of digits containing leading zeros as necessary
22714 * - i : the number of the digits in `d` that are to the left of the decimal point
22715 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d`
22716 *
22717 */
22718function parse(numStr) {
22719 var exponent = 0, digits, numberOfIntegerDigits;
22720 var i, j, zeros;
22721
22722 // Decimal point?
22723 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) {
22724 numStr = numStr.replace(DECIMAL_SEP, '');
22725 }
22726
22727 // Exponential form?
22728 if ((i = numStr.search(/e/i)) > 0) {
22729 // Work out the exponent.
22730 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i;
22731 numberOfIntegerDigits += +numStr.slice(i + 1);
22732 numStr = numStr.substring(0, i);
22733 } else if (numberOfIntegerDigits < 0) {
22734 // There was no decimal point or exponent so it is an integer.
22735 numberOfIntegerDigits = numStr.length;
22736 }
22737
22738 // Count the number of leading zeros.
22739 for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ }
22740
22741 if (i === (zeros = numStr.length)) {
22742 // The digits are all zero.
22743 digits = [0];
22744 numberOfIntegerDigits = 1;
22745 } else {
22746 // Count the number of trailing zeros
22747 zeros--;
22748 while (numStr.charAt(zeros) === ZERO_CHAR) zeros--;
22749
22750 // Trailing zeros are insignificant so ignore them
22751 numberOfIntegerDigits -= i;
22752 digits = [];
22753 // Convert string to array of digits without leading/trailing zeros.
22754 for (j = 0; i <= zeros; i++, j++) {
22755 digits[j] = +numStr.charAt(i);
22756 }
22757 }
22758
22759 // If the number overflows the maximum allowed digits then use an exponent.
22760 if (numberOfIntegerDigits > MAX_DIGITS) {
22761 digits = digits.splice(0, MAX_DIGITS - 1);
22762 exponent = numberOfIntegerDigits - 1;
22763 numberOfIntegerDigits = 1;
22764 }
22765
22766 return { d: digits, e: exponent, i: numberOfIntegerDigits };
22767}
22768
22769/**
22770 * Round the parsed number to the specified number of decimal places
22771 * This function changed the parsedNumber in-place
22772 */
22773function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) {
22774 var digits = parsedNumber.d;
22775 var fractionLen = digits.length - parsedNumber.i;
22776
22777 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number
22778 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize;
22779
22780 // The index of the digit to where rounding is to occur
22781 var roundAt = fractionSize + parsedNumber.i;
22782 var digit = digits[roundAt];
22783
22784 if (roundAt > 0) {
22785 // Drop fractional digits beyond `roundAt`
22786 digits.splice(Math.max(parsedNumber.i, roundAt));
22787
22788 // Set non-fractional digits beyond `roundAt` to 0
22789 for (var j = roundAt; j < digits.length; j++) {
22790 digits[j] = 0;
22791 }
22792 } else {
22793 // We rounded to zero so reset the parsedNumber
22794 fractionLen = Math.max(0, fractionLen);
22795 parsedNumber.i = 1;
22796 digits.length = Math.max(1, roundAt = fractionSize + 1);
22797 digits[0] = 0;
22798 for (var i = 1; i < roundAt; i++) digits[i] = 0;
22799 }
22800
22801 if (digit >= 5) {
22802 if (roundAt - 1 < 0) {
22803 for (var k = 0; k > roundAt; k--) {
22804 digits.unshift(0);
22805 parsedNumber.i++;
22806 }
22807 digits.unshift(1);
22808 parsedNumber.i++;
22809 } else {
22810 digits[roundAt - 1]++;
22811 }
22812 }
22813
22814 // Pad out with zeros to get the required fraction length
22815 for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0);
22816
22817
22818 // Do any carrying, e.g. a digit was rounded up to 10
22819 var carry = digits.reduceRight(function(carry, d, i, digits) {
22820 d = d + carry;
22821 digits[i] = d % 10;
22822 return Math.floor(d / 10);
22823 }, 0);
22824 if (carry) {
22825 digits.unshift(carry);
22826 parsedNumber.i++;
22827 }
22828}
22829
22830/**
22831 * Format a number into a string
22832 * @param {number} number The number to format
22833 * @param {{
22834 * minFrac, // the minimum number of digits required in the fraction part of the number
22835 * maxFrac, // the maximum number of digits required in the fraction part of the number
22836 * gSize, // number of digits in each group of separated digits
22837 * lgSize, // number of digits in the last group of digits before the decimal separator
22838 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`))
22839 * posPre, // the string to go in front of a positive number
22840 * negSuf, // the string to go after a negative number (e.g. `)`)
22841 * posSuf // the string to go after a positive number
22842 * }} pattern
22843 * @param {string} groupSep The string to separate groups of number (e.g. `,`)
22844 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`)
22845 * @param {[type]} fractionSize The size of the fractional part of the number
22846 * @return {string} The number formatted as a string
22847 */
22848function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
22849
22850 if (!(isString(number) || isNumber(number)) || isNaN(number)) return '';
22851
22852 var isInfinity = !isFinite(number);
22853 var isZero = false;
22854 var numStr = Math.abs(number) + '',
22855 formattedText = '',
22856 parsedNumber;
22857
22858 if (isInfinity) {
22859 formattedText = '\u221e';
22860 } else {
22861 parsedNumber = parse(numStr);
22862
22863 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac);
22864
22865 var digits = parsedNumber.d;
22866 var integerLen = parsedNumber.i;
22867 var exponent = parsedNumber.e;
22868 var decimals = [];
22869 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true);
22870
22871 // pad zeros for small numbers
22872 while (integerLen < 0) {
22873 digits.unshift(0);
22874 integerLen++;
22875 }
22876
22877 // extract decimals digits
22878 if (integerLen > 0) {
22879 decimals = digits.splice(integerLen, digits.length);
22880 } else {
22881 decimals = digits;
22882 digits = [0];
22883 }
22884
22885 // format the integer digits with grouping separators
22886 var groups = [];
22887 if (digits.length >= pattern.lgSize) {
22888 groups.unshift(digits.splice(-pattern.lgSize, digits.length).join(''));
22889 }
22890 while (digits.length > pattern.gSize) {
22891 groups.unshift(digits.splice(-pattern.gSize, digits.length).join(''));
22892 }
22893 if (digits.length) {
22894 groups.unshift(digits.join(''));
22895 }
22896 formattedText = groups.join(groupSep);
22897
22898 // append the decimal digits
22899 if (decimals.length) {
22900 formattedText += decimalSep + decimals.join('');
22901 }
22902
22903 if (exponent) {
22904 formattedText += 'e+' + exponent;
22905 }
22906 }
22907 if (number < 0 && !isZero) {
22908 return pattern.negPre + formattedText + pattern.negSuf;
22909 } else {
22910 return pattern.posPre + formattedText + pattern.posSuf;
22911 }
22912}
22913
22914function padNumber(num, digits, trim, negWrap) {
22915 var neg = '';
22916 if (num < 0 || (negWrap && num <= 0)) {
22917 if (negWrap) {
22918 num = -num + 1;
22919 } else {
22920 num = -num;
22921 neg = '-';
22922 }
22923 }
22924 num = '' + num;
22925 while (num.length < digits) num = ZERO_CHAR + num;
22926 if (trim) {
22927 num = num.substr(num.length - digits);
22928 }
22929 return neg + num;
22930}
22931
22932
22933function dateGetter(name, size, offset, trim, negWrap) {
22934 offset = offset || 0;
22935 return function(date) {
22936 var value = date['get' + name]();
22937 if (offset > 0 || value > -offset) {
22938 value += offset;
22939 }
22940 if (value === 0 && offset === -12) value = 12;
22941 return padNumber(value, size, trim, negWrap);
22942 };
22943}
22944
22945function dateStrGetter(name, shortForm, standAlone) {
22946 return function(date, formats) {
22947 var value = date['get' + name]();
22948 var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : '');
22949 var get = uppercase(propPrefix + name);
22950
22951 return formats[get][value];
22952 };
22953}
22954
22955function timeZoneGetter(date, formats, offset) {
22956 var zone = -1 * offset;
22957 var paddedZone = (zone >= 0) ? '+' : '';
22958
22959 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
22960 padNumber(Math.abs(zone % 60), 2);
22961
22962 return paddedZone;
22963}
22964
22965function getFirstThursdayOfYear(year) {
22966 // 0 = index of January
22967 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
22968 // 4 = index of Thursday (+1 to account for 1st = 5)
22969 // 11 = index of *next* Thursday (+1 account for 1st = 12)
22970 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
22971}
22972
22973function getThursdayThisWeek(datetime) {
22974 return new Date(datetime.getFullYear(), datetime.getMonth(),
22975 // 4 = index of Thursday
22976 datetime.getDate() + (4 - datetime.getDay()));
22977}
22978
22979function weekGetter(size) {
22980 return function(date) {
22981 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
22982 thisThurs = getThursdayThisWeek(date);
22983
22984 var diff = +thisThurs - +firstThurs,
22985 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
22986
22987 return padNumber(result, size);
22988 };
22989}
22990
22991function ampmGetter(date, formats) {
22992 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
22993}
22994
22995function eraGetter(date, formats) {
22996 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
22997}
22998
22999function longEraGetter(date, formats) {
23000 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
23001}
23002
23003var DATE_FORMATS = {
23004 yyyy: dateGetter('FullYear', 4, 0, false, true),
23005 yy: dateGetter('FullYear', 2, 0, true, true),
23006 y: dateGetter('FullYear', 1, 0, false, true),
23007 MMMM: dateStrGetter('Month'),
23008 MMM: dateStrGetter('Month', true),
23009 MM: dateGetter('Month', 2, 1),
23010 M: dateGetter('Month', 1, 1),
23011 LLLL: dateStrGetter('Month', false, true),
23012 dd: dateGetter('Date', 2),
23013 d: dateGetter('Date', 1),
23014 HH: dateGetter('Hours', 2),
23015 H: dateGetter('Hours', 1),
23016 hh: dateGetter('Hours', 2, -12),
23017 h: dateGetter('Hours', 1, -12),
23018 mm: dateGetter('Minutes', 2),
23019 m: dateGetter('Minutes', 1),
23020 ss: dateGetter('Seconds', 2),
23021 s: dateGetter('Seconds', 1),
23022 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
23023 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
23024 sss: dateGetter('Milliseconds', 3),
23025 EEEE: dateStrGetter('Day'),
23026 EEE: dateStrGetter('Day', true),
23027 a: ampmGetter,
23028 Z: timeZoneGetter,
23029 ww: weekGetter(2),
23030 w: weekGetter(1),
23031 G: eraGetter,
23032 GG: eraGetter,
23033 GGG: eraGetter,
23034 GGGG: longEraGetter
23035};
23036
23037var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/,
23038 NUMBER_STRING = /^-?\d+$/;
23039
23040/**
23041 * @ngdoc filter
23042 * @name date
23043 * @kind function
23044 *
23045 * @description
23046 * Formats `date` to a string based on the requested `format`.
23047 *
23048 * `format` string can be composed of the following elements:
23049 *
23050 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
23051 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
23052 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
23053 * * `'MMMM'`: Month in year (January-December)
23054 * * `'MMM'`: Month in year (Jan-Dec)
23055 * * `'MM'`: Month in year, padded (01-12)
23056 * * `'M'`: Month in year (1-12)
23057 * * `'LLLL'`: Stand-alone month in year (January-December)
23058 * * `'dd'`: Day in month, padded (01-31)
23059 * * `'d'`: Day in month (1-31)
23060 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
23061 * * `'EEE'`: Day in Week, (Sun-Sat)
23062 * * `'HH'`: Hour in day, padded (00-23)
23063 * * `'H'`: Hour in day (0-23)
23064 * * `'hh'`: Hour in AM/PM, padded (01-12)
23065 * * `'h'`: Hour in AM/PM, (1-12)
23066 * * `'mm'`: Minute in hour, padded (00-59)
23067 * * `'m'`: Minute in hour (0-59)
23068 * * `'ss'`: Second in minute, padded (00-59)
23069 * * `'s'`: Second in minute (0-59)
23070 * * `'sss'`: Millisecond in second, padded (000-999)
23071 * * `'a'`: AM/PM marker
23072 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
23073 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
23074 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
23075 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
23076 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
23077 *
23078 * `format` string can also be one of the following predefined
23079 * {@link guide/i18n localizable formats}:
23080 *
23081 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
23082 * (e.g. Sep 3, 2010 12:05:08 PM)
23083 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
23084 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
23085 * (e.g. Friday, September 3, 2010)
23086 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
23087 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
23088 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
23089 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
23090 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
23091 *
23092 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
23093 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
23094 * (e.g. `"h 'o''clock'"`).
23095 *
23096 * Any other characters in the `format` string will be output as-is.
23097 *
23098 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
23099 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
23100 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
23101 * specified in the string input, the time is considered to be in the local timezone.
23102 * @param {string=} format Formatting rules (see Description). If not specified,
23103 * `mediumDate` is used.
23104 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
23105 * continental US time zone abbreviations, but for general use, use a time zone offset, for
23106 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
23107 * If not specified, the timezone of the browser will be used.
23108 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
23109 *
23110 * @example
23111 <example name="filter-date">
23112 <file name="index.html">
23113 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
23114 <span>{{1288323623006 | date:'medium'}}</span><br>
23115 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
23116 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
23117 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
23118 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
23119 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
23120 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
23121 </file>
23122 <file name="protractor.js" type="protractor">
23123 it('should format date', function() {
23124 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
23125 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
23126 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
23127 toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/);
23128 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
23129 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
23130 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
23131 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
23132 });
23133 </file>
23134 </example>
23135 */
23136dateFilter.$inject = ['$locale'];
23137function dateFilter($locale) {
23138
23139
23140 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
23141 // 1 2 3 4 5 6 7 8 9 10 11
23142 function jsonStringToDate(string) {
23143 var match;
23144 if ((match = string.match(R_ISO8601_STR))) {
23145 var date = new Date(0),
23146 tzHour = 0,
23147 tzMin = 0,
23148 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
23149 timeSetter = match[8] ? date.setUTCHours : date.setHours;
23150
23151 if (match[9]) {
23152 tzHour = toInt(match[9] + match[10]);
23153 tzMin = toInt(match[9] + match[11]);
23154 }
23155 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
23156 var h = toInt(match[4] || 0) - tzHour;
23157 var m = toInt(match[5] || 0) - tzMin;
23158 var s = toInt(match[6] || 0);
23159 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
23160 timeSetter.call(date, h, m, s, ms);
23161 return date;
23162 }
23163 return string;
23164 }
23165
23166
23167 return function(date, format, timezone) {
23168 var text = '',
23169 parts = [],
23170 fn, match;
23171
23172 format = format || 'mediumDate';
23173 format = $locale.DATETIME_FORMATS[format] || format;
23174 if (isString(date)) {
23175 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
23176 }
23177
23178 if (isNumber(date)) {
23179 date = new Date(date);
23180 }
23181
23182 if (!isDate(date) || !isFinite(date.getTime())) {
23183 return date;
23184 }
23185
23186 while (format) {
23187 match = DATE_FORMATS_SPLIT.exec(format);
23188 if (match) {
23189 parts = concat(parts, match, 1);
23190 format = parts.pop();
23191 } else {
23192 parts.push(format);
23193 format = null;
23194 }
23195 }
23196
23197 var dateTimezoneOffset = date.getTimezoneOffset();
23198 if (timezone) {
23199 dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
23200 date = convertTimezoneToLocal(date, timezone, true);
23201 }
23202 forEach(parts, function(value) {
23203 fn = DATE_FORMATS[value];
23204 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
23205 : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
23206 });
23207
23208 return text;
23209 };
23210}
23211
23212
23213/**
23214 * @ngdoc filter
23215 * @name json
23216 * @kind function
23217 *
23218 * @description
23219 * Allows you to convert a JavaScript object into JSON string.
23220 *
23221 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
23222 * the binding is automatically converted to JSON.
23223 *
23224 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
23225 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
23226 * @returns {string} JSON string.
23227 *
23228 *
23229 * @example
23230 <example name="filter-json">
23231 <file name="index.html">
23232 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
23233 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
23234 </file>
23235 <file name="protractor.js" type="protractor">
23236 it('should jsonify filtered objects', function() {
23237 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/);
23238 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/);
23239 });
23240 </file>
23241 </example>
23242 *
23243 */
23244function jsonFilter() {
23245 return function(object, spacing) {
23246 if (isUndefined(spacing)) {
23247 spacing = 2;
23248 }
23249 return toJson(object, spacing);
23250 };
23251}
23252
23253
23254/**
23255 * @ngdoc filter
23256 * @name lowercase
23257 * @kind function
23258 * @description
23259 * Converts string to lowercase.
23260 *
23261 * See the {@link ng.uppercase uppercase filter documentation} for a functionally identical example.
23262 *
23263 * @see angular.lowercase
23264 */
23265var lowercaseFilter = valueFn(lowercase);
23266
23267
23268/**
23269 * @ngdoc filter
23270 * @name uppercase
23271 * @kind function
23272 * @description
23273 * Converts string to uppercase.
23274 * @example
23275 <example module="uppercaseFilterExample" name="filter-uppercase">
23276 <file name="index.html">
23277 <script>
23278 angular.module('uppercaseFilterExample', [])
23279 .controller('ExampleController', ['$scope', function($scope) {
23280 $scope.title = 'This is a title';
23281 }]);
23282 </script>
23283 <div ng-controller="ExampleController">
23284 <!-- This title should be formatted normally -->
23285 <h1>{{title}}</h1>
23286 <!-- This title should be capitalized -->
23287 <h1>{{title | uppercase}}</h1>
23288 </div>
23289 </file>
23290 </example>
23291 */
23292var uppercaseFilter = valueFn(uppercase);
23293
23294/**
23295 * @ngdoc filter
23296 * @name limitTo
23297 * @kind function
23298 *
23299 * @description
23300 * Creates a new array or string containing only a specified number of elements. The elements are
23301 * taken from either the beginning or the end of the source array, string or number, as specified by
23302 * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported
23303 * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input,
23304 * it is converted to a string.
23305 *
23306 * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited.
23307 * @param {string|number} limit - The length of the returned array or string. If the `limit` number
23308 * is positive, `limit` number of items from the beginning of the source array/string are copied.
23309 * If the number is negative, `limit` number of items from the end of the source array/string
23310 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
23311 * the input will be returned unchanged.
23312 * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index,
23313 * `begin` indicates an offset from the end of `input`. Defaults to `0`.
23314 * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had
23315 * less than `limit` elements.
23316 *
23317 * @example
23318 <example module="limitToExample" name="limit-to-filter">
23319 <file name="index.html">
23320 <script>
23321 angular.module('limitToExample', [])
23322 .controller('ExampleController', ['$scope', function($scope) {
23323 $scope.numbers = [1,2,3,4,5,6,7,8,9];
23324 $scope.letters = "abcdefghi";
23325 $scope.longNumber = 2345432342;
23326 $scope.numLimit = 3;
23327 $scope.letterLimit = 3;
23328 $scope.longNumberLimit = 3;
23329 }]);
23330 </script>
23331 <div ng-controller="ExampleController">
23332 <label>
23333 Limit {{numbers}} to:
23334 <input type="number" step="1" ng-model="numLimit">
23335 </label>
23336 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
23337 <label>
23338 Limit {{letters}} to:
23339 <input type="number" step="1" ng-model="letterLimit">
23340 </label>
23341 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
23342 <label>
23343 Limit {{longNumber}} to:
23344 <input type="number" step="1" ng-model="longNumberLimit">
23345 </label>
23346 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
23347 </div>
23348 </file>
23349 <file name="protractor.js" type="protractor">
23350 var numLimitInput = element(by.model('numLimit'));
23351 var letterLimitInput = element(by.model('letterLimit'));
23352 var longNumberLimitInput = element(by.model('longNumberLimit'));
23353 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
23354 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
23355 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
23356
23357 it('should limit the number array to first three items', function() {
23358 expect(numLimitInput.getAttribute('value')).toBe('3');
23359 expect(letterLimitInput.getAttribute('value')).toBe('3');
23360 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
23361 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
23362 expect(limitedLetters.getText()).toEqual('Output letters: abc');
23363 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
23364 });
23365
23366 // There is a bug in safari and protractor that doesn't like the minus key
23367 // it('should update the output when -3 is entered', function() {
23368 // numLimitInput.clear();
23369 // numLimitInput.sendKeys('-3');
23370 // letterLimitInput.clear();
23371 // letterLimitInput.sendKeys('-3');
23372 // longNumberLimitInput.clear();
23373 // longNumberLimitInput.sendKeys('-3');
23374 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
23375 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
23376 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
23377 // });
23378
23379 it('should not exceed the maximum size of input array', function() {
23380 numLimitInput.clear();
23381 numLimitInput.sendKeys('100');
23382 letterLimitInput.clear();
23383 letterLimitInput.sendKeys('100');
23384 longNumberLimitInput.clear();
23385 longNumberLimitInput.sendKeys('100');
23386 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
23387 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
23388 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
23389 });
23390 </file>
23391 </example>
23392*/
23393function limitToFilter() {
23394 return function(input, limit, begin) {
23395 if (Math.abs(Number(limit)) === Infinity) {
23396 limit = Number(limit);
23397 } else {
23398 limit = toInt(limit);
23399 }
23400 if (isNumberNaN(limit)) return input;
23401
23402 if (isNumber(input)) input = input.toString();
23403 if (!isArrayLike(input)) return input;
23404
23405 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
23406 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
23407
23408 if (limit >= 0) {
23409 return sliceFn(input, begin, begin + limit);
23410 } else {
23411 if (begin === 0) {
23412 return sliceFn(input, limit, input.length);
23413 } else {
23414 return sliceFn(input, Math.max(0, begin + limit), begin);
23415 }
23416 }
23417 };
23418}
23419
23420function sliceFn(input, begin, end) {
23421 if (isString(input)) return input.slice(begin, end);
23422
23423 return slice.call(input, begin, end);
23424}
23425
23426/**
23427 * @ngdoc filter
23428 * @name orderBy
23429 * @kind function
23430 *
23431 * @description
23432 * Returns an array containing the items from the specified `collection`, ordered by a `comparator`
23433 * function based on the values computed using the `expression` predicate.
23434 *
23435 * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in
23436 * `[{id: 'bar'}, {id: 'foo'}]`.
23437 *
23438 * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray,
23439 * String, etc).
23440 *
23441 * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker
23442 * for the preceding one. The `expression` is evaluated against each item and the output is used
23443 * for comparing with other items.
23444 *
23445 * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in
23446 * ascending order.
23447 *
23448 * The comparison is done using the `comparator` function. If none is specified, a default, built-in
23449 * comparator is used (see below for details - in a nutshell, it compares numbers numerically and
23450 * strings alphabetically).
23451 *
23452 * ### Under the hood
23453 *
23454 * Ordering the specified `collection` happens in two phases:
23455 *
23456 * 1. All items are passed through the predicate (or predicates), and the returned values are saved
23457 * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed
23458 * through a predicate that extracts the value of the `label` property, would be transformed to:
23459 * ```
23460 * {
23461 * value: 'foo',
23462 * type: 'string',
23463 * index: ...
23464 * }
23465 * ```
23466 * **Note:** `null` values use `'null'` as their type.
23467 * 2. The comparator function is used to sort the items, based on the derived values, types and
23468 * indices.
23469 *
23470 * If you use a custom comparator, it will be called with pairs of objects of the form
23471 * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal
23472 * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the
23473 * second, or `1` otherwise.
23474 *
23475 * In order to ensure that the sorting will be deterministic across platforms, if none of the
23476 * specified predicates can distinguish between two items, `orderBy` will automatically introduce a
23477 * dummy predicate that returns the item's index as `value`.
23478 * (If you are using a custom comparator, make sure it can handle this predicate as well.)
23479 *
23480 * If a custom comparator still can't distinguish between two items, then they will be sorted based
23481 * on their index using the built-in comparator.
23482 *
23483 * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted
23484 * value for an item, `orderBy` will try to convert that object to a primitive value, before passing
23485 * it to the comparator. The following rules govern the conversion:
23486 *
23487 * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be
23488 * used instead.<br />
23489 * (If the object has a `valueOf()` method that returns another object, then the returned object
23490 * will be used in subsequent steps.)
23491 * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that
23492 * returns a primitive, its return value will be used instead.<br />
23493 * (If the object has a `toString()` method that returns another object, then the returned object
23494 * will be used in subsequent steps.)
23495 * 3. No conversion; the object itself is used.
23496 *
23497 * ### The default comparator
23498 *
23499 * The default, built-in comparator should be sufficient for most usecases. In short, it compares
23500 * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to
23501 * using their index in the original collection, sorts values of different types by type and puts
23502 * `undefined` and `null` values at the end of the sorted list.
23503 *
23504 * More specifically, it follows these steps to determine the relative order of items:
23505 *
23506 * 1. If the compared values are of different types:
23507 * - If one of the values is undefined, consider it "greater than" the other.
23508 * - Else if one of the values is null, consider it "greater than" the other.
23509 * - Else compare the types themselves alphabetically.
23510 * 2. If both values are of type `string`, compare them alphabetically in a case- and
23511 * locale-insensitive way.
23512 * 3. If both values are objects, compare their indices instead.
23513 * 4. Otherwise, return:
23514 * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`).
23515 * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator).
23516 * - `1`, otherwise.
23517 *
23518 * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being
23519 * saved as numbers and not strings.
23520 * **Note:** For the purpose of sorting, `null` and `undefined` are considered "greater than"
23521 * any other value (with undefined "greater than" null). This effectively means that `null`
23522 * and `undefined` values end up at the end of a list sorted in ascending order.
23523 * **Note:** `null` values use `'null'` as their type to be able to distinguish them from objects.
23524 *
23525 * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort.
23526 * @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of
23527 * predicates) to be used by the comparator to determine the order of elements.
23528 *
23529 * Can be one of:
23530 *
23531 * - `Function`: A getter function. This function will be called with each item as argument and
23532 * the return value will be used for sorting.
23533 * - `string`: An AngularJS expression. This expression will be evaluated against each item and the
23534 * result will be used for sorting. For example, use `'label'` to sort by a property called
23535 * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label`
23536 * property.<br />
23537 * (The result of a constant expression is interpreted as a property name to be used for
23538 * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a
23539 * property called `special name`.)<br />
23540 * An expression can be optionally prefixed with `+` or `-` to control the sorting direction,
23541 * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided,
23542 * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons.
23543 * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the
23544 * relative order of two items, the next predicate is used as a tie-breaker.
23545 *
23546 * **Note:** If the predicate is missing or empty then it defaults to `'+'`.
23547 *
23548 * @param {boolean=} reverse - If `true`, reverse the sorting order.
23549 * @param {(Function)=} comparator - The comparator function used to determine the relative order of
23550 * value pairs. If omitted, the built-in comparator will be used.
23551 *
23552 * @returns {Array} - The sorted array.
23553 *
23554 *
23555 * @example
23556 * ### Ordering a table with `ngRepeat`
23557 *
23558 * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by
23559 * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means
23560 * it defaults to the built-in comparator.
23561 *
23562 <example name="orderBy-static" module="orderByExample1">
23563 <file name="index.html">
23564 <div ng-controller="ExampleController">
23565 <table class="friends">
23566 <tr>
23567 <th>Name</th>
23568 <th>Phone Number</th>
23569 <th>Age</th>
23570 </tr>
23571 <tr ng-repeat="friend in friends | orderBy:'-age'">
23572 <td>{{friend.name}}</td>
23573 <td>{{friend.phone}}</td>
23574 <td>{{friend.age}}</td>
23575 </tr>
23576 </table>
23577 </div>
23578 </file>
23579 <file name="script.js">
23580 angular.module('orderByExample1', [])
23581 .controller('ExampleController', ['$scope', function($scope) {
23582 $scope.friends = [
23583 {name: 'John', phone: '555-1212', age: 10},
23584 {name: 'Mary', phone: '555-9876', age: 19},
23585 {name: 'Mike', phone: '555-4321', age: 21},
23586 {name: 'Adam', phone: '555-5678', age: 35},
23587 {name: 'Julie', phone: '555-8765', age: 29}
23588 ];
23589 }]);
23590 </file>
23591 <file name="style.css">
23592 .friends {
23593 border-collapse: collapse;
23594 }
23595
23596 .friends th {
23597 border-bottom: 1px solid;
23598 }
23599 .friends td, .friends th {
23600 border-left: 1px solid;
23601 padding: 5px 10px;
23602 }
23603 .friends td:first-child, .friends th:first-child {
23604 border-left: none;
23605 }
23606 </file>
23607 <file name="protractor.js" type="protractor">
23608 // Element locators
23609 var names = element.all(by.repeater('friends').column('friend.name'));
23610
23611 it('should sort friends by age in reverse order', function() {
23612 expect(names.get(0).getText()).toBe('Adam');
23613 expect(names.get(1).getText()).toBe('Julie');
23614 expect(names.get(2).getText()).toBe('Mike');
23615 expect(names.get(3).getText()).toBe('Mary');
23616 expect(names.get(4).getText()).toBe('John');
23617 });
23618 </file>
23619 </example>
23620 * <hr />
23621 *
23622 * @example
23623 * ### Changing parameters dynamically
23624 *
23625 * All parameters can be changed dynamically. The next example shows how you can make the columns of
23626 * a table sortable, by binding the `expression` and `reverse` parameters to scope properties.
23627 *
23628 <example name="orderBy-dynamic" module="orderByExample2">
23629 <file name="index.html">
23630 <div ng-controller="ExampleController">
23631 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
23632 <hr/>
23633 <button ng-click="propertyName = null; reverse = false">Set to unsorted</button>
23634 <hr/>
23635 <table class="friends">
23636 <tr>
23637 <th>
23638 <button ng-click="sortBy('name')">Name</button>
23639 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
23640 </th>
23641 <th>
23642 <button ng-click="sortBy('phone')">Phone Number</button>
23643 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
23644 </th>
23645 <th>
23646 <button ng-click="sortBy('age')">Age</button>
23647 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
23648 </th>
23649 </tr>
23650 <tr ng-repeat="friend in friends | orderBy:propertyName:reverse">
23651 <td>{{friend.name}}</td>
23652 <td>{{friend.phone}}</td>
23653 <td>{{friend.age}}</td>
23654 </tr>
23655 </table>
23656 </div>
23657 </file>
23658 <file name="script.js">
23659 angular.module('orderByExample2', [])
23660 .controller('ExampleController', ['$scope', function($scope) {
23661 var friends = [
23662 {name: 'John', phone: '555-1212', age: 10},
23663 {name: 'Mary', phone: '555-9876', age: 19},
23664 {name: 'Mike', phone: '555-4321', age: 21},
23665 {name: 'Adam', phone: '555-5678', age: 35},
23666 {name: 'Julie', phone: '555-8765', age: 29}
23667 ];
23668
23669 $scope.propertyName = 'age';
23670 $scope.reverse = true;
23671 $scope.friends = friends;
23672
23673 $scope.sortBy = function(propertyName) {
23674 $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false;
23675 $scope.propertyName = propertyName;
23676 };
23677 }]);
23678 </file>
23679 <file name="style.css">
23680 .friends {
23681 border-collapse: collapse;
23682 }
23683
23684 .friends th {
23685 border-bottom: 1px solid;
23686 }
23687 .friends td, .friends th {
23688 border-left: 1px solid;
23689 padding: 5px 10px;
23690 }
23691 .friends td:first-child, .friends th:first-child {
23692 border-left: none;
23693 }
23694
23695 .sortorder:after {
23696 content: '\25b2'; // BLACK UP-POINTING TRIANGLE
23697 }
23698 .sortorder.reverse:after {
23699 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
23700 }
23701 </file>
23702 <file name="protractor.js" type="protractor">
23703 // Element locators
23704 var unsortButton = element(by.partialButtonText('unsorted'));
23705 var nameHeader = element(by.partialButtonText('Name'));
23706 var phoneHeader = element(by.partialButtonText('Phone'));
23707 var ageHeader = element(by.partialButtonText('Age'));
23708 var firstName = element(by.repeater('friends').column('friend.name').row(0));
23709 var lastName = element(by.repeater('friends').column('friend.name').row(4));
23710
23711 it('should sort friends by some property, when clicking on the column header', function() {
23712 expect(firstName.getText()).toBe('Adam');
23713 expect(lastName.getText()).toBe('John');
23714
23715 phoneHeader.click();
23716 expect(firstName.getText()).toBe('John');
23717 expect(lastName.getText()).toBe('Mary');
23718
23719 nameHeader.click();
23720 expect(firstName.getText()).toBe('Adam');
23721 expect(lastName.getText()).toBe('Mike');
23722
23723 ageHeader.click();
23724 expect(firstName.getText()).toBe('John');
23725 expect(lastName.getText()).toBe('Adam');
23726 });
23727
23728 it('should sort friends in reverse order, when clicking on the same column', function() {
23729 expect(firstName.getText()).toBe('Adam');
23730 expect(lastName.getText()).toBe('John');
23731
23732 ageHeader.click();
23733 expect(firstName.getText()).toBe('John');
23734 expect(lastName.getText()).toBe('Adam');
23735
23736 ageHeader.click();
23737 expect(firstName.getText()).toBe('Adam');
23738 expect(lastName.getText()).toBe('John');
23739 });
23740
23741 it('should restore the original order, when clicking "Set to unsorted"', function() {
23742 expect(firstName.getText()).toBe('Adam');
23743 expect(lastName.getText()).toBe('John');
23744
23745 unsortButton.click();
23746 expect(firstName.getText()).toBe('John');
23747 expect(lastName.getText()).toBe('Julie');
23748 });
23749 </file>
23750 </example>
23751 * <hr />
23752 *
23753 * @example
23754 * ### Using `orderBy` inside a controller
23755 *
23756 * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and
23757 * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory
23758 * and retrieve the `orderBy` filter with `$filter('orderBy')`.)
23759 *
23760 <example name="orderBy-call-manually" module="orderByExample3">
23761 <file name="index.html">
23762 <div ng-controller="ExampleController">
23763 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
23764 <hr/>
23765 <button ng-click="sortBy(null)">Set to unsorted</button>
23766 <hr/>
23767 <table class="friends">
23768 <tr>
23769 <th>
23770 <button ng-click="sortBy('name')">Name</button>
23771 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
23772 </th>
23773 <th>
23774 <button ng-click="sortBy('phone')">Phone Number</button>
23775 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
23776 </th>
23777 <th>
23778 <button ng-click="sortBy('age')">Age</button>
23779 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
23780 </th>
23781 </tr>
23782 <tr ng-repeat="friend in friends">
23783 <td>{{friend.name}}</td>
23784 <td>{{friend.phone}}</td>
23785 <td>{{friend.age}}</td>
23786 </tr>
23787 </table>
23788 </div>
23789 </file>
23790 <file name="script.js">
23791 angular.module('orderByExample3', [])
23792 .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) {
23793 var friends = [
23794 {name: 'John', phone: '555-1212', age: 10},
23795 {name: 'Mary', phone: '555-9876', age: 19},
23796 {name: 'Mike', phone: '555-4321', age: 21},
23797 {name: 'Adam', phone: '555-5678', age: 35},
23798 {name: 'Julie', phone: '555-8765', age: 29}
23799 ];
23800
23801 $scope.propertyName = 'age';
23802 $scope.reverse = true;
23803 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
23804
23805 $scope.sortBy = function(propertyName) {
23806 $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName)
23807 ? !$scope.reverse : false;
23808 $scope.propertyName = propertyName;
23809 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
23810 };
23811 }]);
23812 </file>
23813 <file name="style.css">
23814 .friends {
23815 border-collapse: collapse;
23816 }
23817
23818 .friends th {
23819 border-bottom: 1px solid;
23820 }
23821 .friends td, .friends th {
23822 border-left: 1px solid;
23823 padding: 5px 10px;
23824 }
23825 .friends td:first-child, .friends th:first-child {
23826 border-left: none;
23827 }
23828
23829 .sortorder:after {
23830 content: '\25b2'; // BLACK UP-POINTING TRIANGLE
23831 }
23832 .sortorder.reverse:after {
23833 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
23834 }
23835 </file>
23836 <file name="protractor.js" type="protractor">
23837 // Element locators
23838 var unsortButton = element(by.partialButtonText('unsorted'));
23839 var nameHeader = element(by.partialButtonText('Name'));
23840 var phoneHeader = element(by.partialButtonText('Phone'));
23841 var ageHeader = element(by.partialButtonText('Age'));
23842 var firstName = element(by.repeater('friends').column('friend.name').row(0));
23843 var lastName = element(by.repeater('friends').column('friend.name').row(4));
23844
23845 it('should sort friends by some property, when clicking on the column header', function() {
23846 expect(firstName.getText()).toBe('Adam');
23847 expect(lastName.getText()).toBe('John');
23848
23849 phoneHeader.click();
23850 expect(firstName.getText()).toBe('John');
23851 expect(lastName.getText()).toBe('Mary');
23852
23853 nameHeader.click();
23854 expect(firstName.getText()).toBe('Adam');
23855 expect(lastName.getText()).toBe('Mike');
23856
23857 ageHeader.click();
23858 expect(firstName.getText()).toBe('John');
23859 expect(lastName.getText()).toBe('Adam');
23860 });
23861
23862 it('should sort friends in reverse order, when clicking on the same column', function() {
23863 expect(firstName.getText()).toBe('Adam');
23864 expect(lastName.getText()).toBe('John');
23865
23866 ageHeader.click();
23867 expect(firstName.getText()).toBe('John');
23868 expect(lastName.getText()).toBe('Adam');
23869
23870 ageHeader.click();
23871 expect(firstName.getText()).toBe('Adam');
23872 expect(lastName.getText()).toBe('John');
23873 });
23874
23875 it('should restore the original order, when clicking "Set to unsorted"', function() {
23876 expect(firstName.getText()).toBe('Adam');
23877 expect(lastName.getText()).toBe('John');
23878
23879 unsortButton.click();
23880 expect(firstName.getText()).toBe('John');
23881 expect(lastName.getText()).toBe('Julie');
23882 });
23883 </file>
23884 </example>
23885 * <hr />
23886 *
23887 * @example
23888 * ### Using a custom comparator
23889 *
23890 * If you have very specific requirements about the way items are sorted, you can pass your own
23891 * comparator function. For example, you might need to compare some strings in a locale-sensitive
23892 * way. (When specifying a custom comparator, you also need to pass a value for the `reverse`
23893 * argument - passing `false` retains the default sorting order, i.e. ascending.)
23894 *
23895 <example name="orderBy-custom-comparator" module="orderByExample4">
23896 <file name="index.html">
23897 <div ng-controller="ExampleController">
23898 <div class="friends-container custom-comparator">
23899 <h3>Locale-sensitive Comparator</h3>
23900 <table class="friends">
23901 <tr>
23902 <th>Name</th>
23903 <th>Favorite Letter</th>
23904 </tr>
23905 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator">
23906 <td>{{friend.name}}</td>
23907 <td>{{friend.favoriteLetter}}</td>
23908 </tr>
23909 </table>
23910 </div>
23911 <div class="friends-container default-comparator">
23912 <h3>Default Comparator</h3>
23913 <table class="friends">
23914 <tr>
23915 <th>Name</th>
23916 <th>Favorite Letter</th>
23917 </tr>
23918 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter'">
23919 <td>{{friend.name}}</td>
23920 <td>{{friend.favoriteLetter}}</td>
23921 </tr>
23922 </table>
23923 </div>
23924 </div>
23925 </file>
23926 <file name="script.js">
23927 angular.module('orderByExample4', [])
23928 .controller('ExampleController', ['$scope', function($scope) {
23929 $scope.friends = [
23930 {name: 'John', favoriteLetter: 'Ä'},
23931 {name: 'Mary', favoriteLetter: 'Ü'},
23932 {name: 'Mike', favoriteLetter: 'Ö'},
23933 {name: 'Adam', favoriteLetter: 'H'},
23934 {name: 'Julie', favoriteLetter: 'Z'}
23935 ];
23936
23937 $scope.localeSensitiveComparator = function(v1, v2) {
23938 // If we don't get strings, just compare by index
23939 if (v1.type !== 'string' || v2.type !== 'string') {
23940 return (v1.index < v2.index) ? -1 : 1;
23941 }
23942
23943 // Compare strings alphabetically, taking locale into account
23944 return v1.value.localeCompare(v2.value);
23945 };
23946 }]);
23947 </file>
23948 <file name="style.css">
23949 .friends-container {
23950 display: inline-block;
23951 margin: 0 30px;
23952 }
23953
23954 .friends {
23955 border-collapse: collapse;
23956 }
23957
23958 .friends th {
23959 border-bottom: 1px solid;
23960 }
23961 .friends td, .friends th {
23962 border-left: 1px solid;
23963 padding: 5px 10px;
23964 }
23965 .friends td:first-child, .friends th:first-child {
23966 border-left: none;
23967 }
23968 </file>
23969 <file name="protractor.js" type="protractor">
23970 // Element locators
23971 var container = element(by.css('.custom-comparator'));
23972 var names = container.all(by.repeater('friends').column('friend.name'));
23973
23974 it('should sort friends by favorite letter (in correct alphabetical order)', function() {
23975 expect(names.get(0).getText()).toBe('John');
23976 expect(names.get(1).getText()).toBe('Adam');
23977 expect(names.get(2).getText()).toBe('Mike');
23978 expect(names.get(3).getText()).toBe('Mary');
23979 expect(names.get(4).getText()).toBe('Julie');
23980 });
23981 </file>
23982 </example>
23983 *
23984 */
23985orderByFilter.$inject = ['$parse'];
23986function orderByFilter($parse) {
23987 return function(array, sortPredicate, reverseOrder, compareFn) {
23988
23989 if (array == null) return array;
23990 if (!isArrayLike(array)) {
23991 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array);
23992 }
23993
23994 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
23995 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
23996
23997 var predicates = processPredicates(sortPredicate);
23998
23999 var descending = reverseOrder ? -1 : 1;
24000
24001 // Define the `compare()` function. Use a default comparator if none is specified.
24002 var compare = isFunction(compareFn) ? compareFn : defaultCompare;
24003
24004 // The next three lines are a version of a Swartzian Transform idiom from Perl
24005 // (sometimes called the Decorate-Sort-Undecorate idiom)
24006 // See https://en.wikipedia.org/wiki/Schwartzian_transform
24007 var compareValues = Array.prototype.map.call(array, getComparisonObject);
24008 compareValues.sort(doComparison);
24009 array = compareValues.map(function(item) { return item.value; });
24010
24011 return array;
24012
24013 function getComparisonObject(value, index) {
24014 // NOTE: We are adding an extra `tieBreaker` value based on the element's index.
24015 // This will be used to keep the sort stable when none of the input predicates can
24016 // distinguish between two elements.
24017 return {
24018 value: value,
24019 tieBreaker: {value: index, type: 'number', index: index},
24020 predicateValues: predicates.map(function(predicate) {
24021 return getPredicateValue(predicate.get(value), index);
24022 })
24023 };
24024 }
24025
24026 function doComparison(v1, v2) {
24027 for (var i = 0, ii = predicates.length; i < ii; i++) {
24028 var result = compare(v1.predicateValues[i], v2.predicateValues[i]);
24029 if (result) {
24030 return result * predicates[i].descending * descending;
24031 }
24032 }
24033
24034 return (compare(v1.tieBreaker, v2.tieBreaker) || defaultCompare(v1.tieBreaker, v2.tieBreaker)) * descending;
24035 }
24036 };
24037
24038 function processPredicates(sortPredicates) {
24039 return sortPredicates.map(function(predicate) {
24040 var descending = 1, get = identity;
24041
24042 if (isFunction(predicate)) {
24043 get = predicate;
24044 } else if (isString(predicate)) {
24045 if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) {
24046 descending = predicate.charAt(0) === '-' ? -1 : 1;
24047 predicate = predicate.substring(1);
24048 }
24049 if (predicate !== '') {
24050 get = $parse(predicate);
24051 if (get.constant) {
24052 var key = get();
24053 get = function(value) { return value[key]; };
24054 }
24055 }
24056 }
24057 return {get: get, descending: descending};
24058 });
24059 }
24060
24061 function isPrimitive(value) {
24062 switch (typeof value) {
24063 case 'number': /* falls through */
24064 case 'boolean': /* falls through */
24065 case 'string':
24066 return true;
24067 default:
24068 return false;
24069 }
24070 }
24071
24072 function objectValue(value) {
24073 // If `valueOf` is a valid function use that
24074 if (isFunction(value.valueOf)) {
24075 value = value.valueOf();
24076 if (isPrimitive(value)) return value;
24077 }
24078 // If `toString` is a valid function and not the one from `Object.prototype` use that
24079 if (hasCustomToString(value)) {
24080 value = value.toString();
24081 if (isPrimitive(value)) return value;
24082 }
24083
24084 return value;
24085 }
24086
24087 function getPredicateValue(value, index) {
24088 var type = typeof value;
24089 if (value === null) {
24090 type = 'null';
24091 } else if (type === 'object') {
24092 value = objectValue(value);
24093 }
24094 return {value: value, type: type, index: index};
24095 }
24096
24097 function defaultCompare(v1, v2) {
24098 var result = 0;
24099 var type1 = v1.type;
24100 var type2 = v2.type;
24101
24102 if (type1 === type2) {
24103 var value1 = v1.value;
24104 var value2 = v2.value;
24105
24106 if (type1 === 'string') {
24107 // Compare strings case-insensitively
24108 value1 = value1.toLowerCase();
24109 value2 = value2.toLowerCase();
24110 } else if (type1 === 'object') {
24111 // For basic objects, use the position of the object
24112 // in the collection instead of the value
24113 if (isObject(value1)) value1 = v1.index;
24114 if (isObject(value2)) value2 = v2.index;
24115 }
24116
24117 if (value1 !== value2) {
24118 result = value1 < value2 ? -1 : 1;
24119 }
24120 } else {
24121 result = (type1 === 'undefined') ? 1 :
24122 (type2 === 'undefined') ? -1 :
24123 (type1 === 'null') ? 1 :
24124 (type2 === 'null') ? -1 :
24125 (type1 < type2) ? -1 : 1;
24126 }
24127
24128 return result;
24129 }
24130}
24131
24132function ngDirective(directive) {
24133 if (isFunction(directive)) {
24134 directive = {
24135 link: directive
24136 };
24137 }
24138 directive.restrict = directive.restrict || 'AC';
24139 return valueFn(directive);
24140}
24141
24142/**
24143 * @ngdoc directive
24144 * @name a
24145 * @restrict E
24146 *
24147 * @description
24148 * Modifies the default behavior of the html a tag so that the default action is prevented when
24149 * the href attribute is empty.
24150 *
24151 * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive.
24152 */
24153var htmlAnchorDirective = valueFn({
24154 restrict: 'E',
24155 compile: function(element, attr) {
24156 if (!attr.href && !attr.xlinkHref) {
24157 return function(scope, element) {
24158 // If the linked element is not an anchor tag anymore, do nothing
24159 if (element[0].nodeName.toLowerCase() !== 'a') return;
24160
24161 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
24162 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
24163 'xlink:href' : 'href';
24164 element.on('click', function(event) {
24165 // if we have no href url, then don't navigate anywhere.
24166 if (!element.attr(href)) {
24167 event.preventDefault();
24168 }
24169 });
24170 };
24171 }
24172 }
24173});
24174
24175/**
24176 * @ngdoc directive
24177 * @name ngHref
24178 * @restrict A
24179 * @priority 99
24180 *
24181 * @description
24182 * Using AngularJS markup like `{{hash}}` in an href attribute will
24183 * make the link go to the wrong URL if the user clicks it before
24184 * AngularJS has a chance to replace the `{{hash}}` markup with its
24185 * value. Until AngularJS replaces the markup the link will be broken
24186 * and will most likely return a 404 error. The `ngHref` directive
24187 * solves this problem.
24188 *
24189 * The wrong way to write it:
24190 * ```html
24191 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
24192 * ```
24193 *
24194 * The correct way to write it:
24195 * ```html
24196 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
24197 * ```
24198 *
24199 * @element A
24200 * @param {template} ngHref any string which can contain `{{}}` markup.
24201 *
24202 * @example
24203 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
24204 * in links and their different behaviors:
24205 <example name="ng-href">
24206 <file name="index.html">
24207 <input ng-model="value" /><br />
24208 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
24209 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
24210 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
24211 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
24212 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
24213 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
24214 </file>
24215 <file name="protractor.js" type="protractor">
24216 it('should execute ng-click but not reload when href without value', function() {
24217 element(by.id('link-1')).click();
24218 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
24219 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
24220 });
24221
24222 it('should execute ng-click but not reload when href empty string', function() {
24223 element(by.id('link-2')).click();
24224 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
24225 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
24226 });
24227
24228 it('should execute ng-click and change url when ng-href specified', function() {
24229 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
24230
24231 element(by.id('link-3')).click();
24232
24233 // At this point, we navigate away from an AngularJS page, so we need
24234 // to use browser.driver to get the base webdriver.
24235
24236 browser.wait(function() {
24237 return browser.driver.getCurrentUrl().then(function(url) {
24238 return url.match(/\/123$/);
24239 });
24240 }, 5000, 'page should navigate to /123');
24241 });
24242
24243 it('should execute ng-click but not reload when href empty string and name specified', function() {
24244 element(by.id('link-4')).click();
24245 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
24246 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
24247 });
24248
24249 it('should execute ng-click but not reload when no href but name specified', function() {
24250 element(by.id('link-5')).click();
24251 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
24252 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
24253 });
24254
24255 it('should only change url when only ng-href', function() {
24256 element(by.model('value')).clear();
24257 element(by.model('value')).sendKeys('6');
24258 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
24259
24260 element(by.id('link-6')).click();
24261
24262 // At this point, we navigate away from an AngularJS page, so we need
24263 // to use browser.driver to get the base webdriver.
24264 browser.wait(function() {
24265 return browser.driver.getCurrentUrl().then(function(url) {
24266 return url.match(/\/6$/);
24267 });
24268 }, 5000, 'page should navigate to /6');
24269 });
24270 </file>
24271 </example>
24272 */
24273
24274/**
24275 * @ngdoc directive
24276 * @name ngSrc
24277 * @restrict A
24278 * @priority 99
24279 *
24280 * @description
24281 * Using AngularJS markup like `{{hash}}` in a `src` attribute doesn't
24282 * work right: The browser will fetch from the URL with the literal
24283 * text `{{hash}}` until AngularJS replaces the expression inside
24284 * `{{hash}}`. The `ngSrc` directive solves this problem.
24285 *
24286 * The buggy way to write it:
24287 * ```html
24288 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
24289 * ```
24290 *
24291 * The correct way to write it:
24292 * ```html
24293 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
24294 * ```
24295 *
24296 * @element IMG
24297 * @param {template} ngSrc any string which can contain `{{}}` markup.
24298 */
24299
24300/**
24301 * @ngdoc directive
24302 * @name ngSrcset
24303 * @restrict A
24304 * @priority 99
24305 *
24306 * @description
24307 * Using AngularJS markup like `{{hash}}` in a `srcset` attribute doesn't
24308 * work right: The browser will fetch from the URL with the literal
24309 * text `{{hash}}` until AngularJS replaces the expression inside
24310 * `{{hash}}`. The `ngSrcset` directive solves this problem.
24311 *
24312 * The buggy way to write it:
24313 * ```html
24314 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
24315 * ```
24316 *
24317 * The correct way to write it:
24318 * ```html
24319 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
24320 * ```
24321 *
24322 * @element IMG
24323 * @param {template} ngSrcset any string which can contain `{{}}` markup.
24324 */
24325
24326/**
24327 * @ngdoc directive
24328 * @name ngDisabled
24329 * @restrict A
24330 * @priority 100
24331 *
24332 * @description
24333 *
24334 * This directive sets the `disabled` attribute on the element (typically a form control,
24335 * e.g. `input`, `button`, `select` etc.) if the
24336 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
24337 *
24338 * A special directive is necessary because we cannot use interpolation inside the `disabled`
24339 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
24340 *
24341 * @example
24342 <example name="ng-disabled">
24343 <file name="index.html">
24344 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
24345 <button ng-model="button" ng-disabled="checked">Button</button>
24346 </file>
24347 <file name="protractor.js" type="protractor">
24348 it('should toggle button', function() {
24349 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
24350 element(by.model('checked')).click();
24351 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
24352 });
24353 </file>
24354 </example>
24355 *
24356 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
24357 * then the `disabled` attribute will be set on the element
24358 */
24359
24360
24361/**
24362 * @ngdoc directive
24363 * @name ngChecked
24364 * @restrict A
24365 * @priority 100
24366 *
24367 * @description
24368 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
24369 *
24370 * Note that this directive should not be used together with {@link ngModel `ngModel`},
24371 * as this can lead to unexpected behavior.
24372 *
24373 * A special directive is necessary because we cannot use interpolation inside the `checked`
24374 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
24375 *
24376 * @example
24377 <example name="ng-checked">
24378 <file name="index.html">
24379 <label>Check me to check both: <input type="checkbox" ng-model="leader"></label><br/>
24380 <input id="checkFollower" type="checkbox" ng-checked="leader" aria-label="Follower input">
24381 </file>
24382 <file name="protractor.js" type="protractor">
24383 it('should check both checkBoxes', function() {
24384 expect(element(by.id('checkFollower')).getAttribute('checked')).toBeFalsy();
24385 element(by.model('leader')).click();
24386 expect(element(by.id('checkFollower')).getAttribute('checked')).toBeTruthy();
24387 });
24388 </file>
24389 </example>
24390 *
24391 * @element INPUT
24392 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
24393 * then the `checked` attribute will be set on the element
24394 */
24395
24396
24397/**
24398 * @ngdoc directive
24399 * @name ngReadonly
24400 * @restrict A
24401 * @priority 100
24402 *
24403 * @description
24404 *
24405 * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy.
24406 * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on
24407 * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information.
24408 *
24409 * A special directive is necessary because we cannot use interpolation inside the `readonly`
24410 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
24411 *
24412 * @example
24413 <example name="ng-readonly">
24414 <file name="index.html">
24415 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
24416 <input type="text" ng-readonly="checked" value="I'm AngularJS" aria-label="Readonly field" />
24417 </file>
24418 <file name="protractor.js" type="protractor">
24419 it('should toggle readonly attr', function() {
24420 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
24421 element(by.model('checked')).click();
24422 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
24423 });
24424 </file>
24425 </example>
24426 *
24427 * @element INPUT
24428 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
24429 * then special attribute "readonly" will be set on the element
24430 */
24431
24432
24433/**
24434 * @ngdoc directive
24435 * @name ngSelected
24436 * @restrict A
24437 * @priority 100
24438 *
24439 * @description
24440 *
24441 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy.
24442 *
24443 * A special directive is necessary because we cannot use interpolation inside the `selected`
24444 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
24445 *
24446 * <div class="alert alert-warning">
24447 * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only
24448 * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you
24449 * should not use `ngSelected` on the options, as `ngModel` will set the select value and
24450 * selected options.
24451 * </div>
24452 *
24453 * @example
24454 <example name="ng-selected">
24455 <file name="index.html">
24456 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
24457 <select aria-label="ngSelected demo">
24458 <option>Hello!</option>
24459 <option id="greet" ng-selected="selected">Greetings!</option>
24460 </select>
24461 </file>
24462 <file name="protractor.js" type="protractor">
24463 it('should select Greetings!', function() {
24464 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
24465 element(by.model('selected')).click();
24466 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
24467 });
24468 </file>
24469 </example>
24470 *
24471 * @element OPTION
24472 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
24473 * then special attribute "selected" will be set on the element
24474 */
24475
24476/**
24477 * @ngdoc directive
24478 * @name ngOpen
24479 * @restrict A
24480 * @priority 100
24481 *
24482 * @description
24483 *
24484 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy.
24485 *
24486 * A special directive is necessary because we cannot use interpolation inside the `open`
24487 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
24488 *
24489 * ## A note about browser compatibility
24490 *
24491 * Internet Explorer and Edge do not support the `details` element, it is
24492 * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead.
24493 *
24494 * @example
24495 <example name="ng-open">
24496 <file name="index.html">
24497 <label>Toggle details: <input type="checkbox" ng-model="open"></label><br/>
24498 <details id="details" ng-open="open">
24499 <summary>List</summary>
24500 <ul>
24501 <li>Apple</li>
24502 <li>Orange</li>
24503 <li>Durian</li>
24504 </ul>
24505 </details>
24506 </file>
24507 <file name="protractor.js" type="protractor">
24508 it('should toggle open', function() {
24509 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
24510 element(by.model('open')).click();
24511 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
24512 });
24513 </file>
24514 </example>
24515 *
24516 * @element DETAILS
24517 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
24518 * then special attribute "open" will be set on the element
24519 */
24520
24521var ngAttributeAliasDirectives = {};
24522
24523// boolean attrs are evaluated
24524forEach(BOOLEAN_ATTR, function(propName, attrName) {
24525 // binding to multiple is not supported
24526 if (propName === 'multiple') return;
24527
24528 function defaultLinkFn(scope, element, attr) {
24529 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
24530 attr.$set(attrName, !!value);
24531 });
24532 }
24533
24534 var normalized = directiveNormalize('ng-' + attrName);
24535 var linkFn = defaultLinkFn;
24536
24537 if (propName === 'checked') {
24538 linkFn = function(scope, element, attr) {
24539 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
24540 if (attr.ngModel !== attr[normalized]) {
24541 defaultLinkFn(scope, element, attr);
24542 }
24543 };
24544 }
24545
24546 ngAttributeAliasDirectives[normalized] = function() {
24547 return {
24548 restrict: 'A',
24549 priority: 100,
24550 link: linkFn
24551 };
24552 };
24553});
24554
24555// aliased input attrs are evaluated
24556forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
24557 ngAttributeAliasDirectives[ngAttr] = function() {
24558 return {
24559 priority: 100,
24560 link: function(scope, element, attr) {
24561 //special case ngPattern when a literal regular expression value
24562 //is used as the expression (this way we don't have to watch anything).
24563 if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') {
24564 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
24565 if (match) {
24566 attr.$set('ngPattern', new RegExp(match[1], match[2]));
24567 return;
24568 }
24569 }
24570
24571 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
24572 attr.$set(ngAttr, value);
24573 });
24574 }
24575 };
24576 };
24577});
24578
24579// ng-src, ng-srcset, ng-href are interpolated
24580forEach(['src', 'srcset', 'href'], function(attrName) {
24581 var normalized = directiveNormalize('ng-' + attrName);
24582 ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) {
24583 return {
24584 priority: 99, // it needs to run after the attributes are interpolated
24585 link: function(scope, element, attr) {
24586 var propName = attrName,
24587 name = attrName;
24588
24589 if (attrName === 'href' &&
24590 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
24591 name = 'xlinkHref';
24592 attr.$attr[name] = 'xlink:href';
24593 propName = null;
24594 }
24595
24596 // We need to sanitize the url at least once, in case it is a constant
24597 // non-interpolated attribute.
24598 attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized]));
24599
24600 attr.$observe(normalized, function(value) {
24601 if (!value) {
24602 if (attrName === 'href') {
24603 attr.$set(name, null);
24604 }
24605 return;
24606 }
24607
24608 attr.$set(name, value);
24609
24610 // Support: IE 9-11 only
24611 // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
24612 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
24613 // to set the property as well to achieve the desired effect.
24614 // We use attr[attrName] value since $set might have sanitized the url.
24615 if (msie && propName) element.prop(propName, attr[name]);
24616 });
24617 }
24618 };
24619 }];
24620});
24621
24622/* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS
24623 */
24624var nullFormCtrl = {
24625 $addControl: noop,
24626 $getControls: valueFn([]),
24627 $$renameControl: nullFormRenameControl,
24628 $removeControl: noop,
24629 $setValidity: noop,
24630 $setDirty: noop,
24631 $setPristine: noop,
24632 $setSubmitted: noop,
24633 $$setSubmitted: noop
24634},
24635PENDING_CLASS = 'ng-pending',
24636SUBMITTED_CLASS = 'ng-submitted';
24637
24638function nullFormRenameControl(control, name) {
24639 control.$name = name;
24640}
24641
24642/**
24643 * @ngdoc type
24644 * @name form.FormController
24645 *
24646 * @property {boolean} $pristine True if user has not interacted with the form yet.
24647 * @property {boolean} $dirty True if user has already interacted with the form.
24648 * @property {boolean} $valid True if all of the containing forms and controls are valid.
24649 * @property {boolean} $invalid True if at least one containing control or form is invalid.
24650 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
24651 *
24652 * @property {Object} $pending An object hash, containing references to controls or forms with
24653 * pending validators, where:
24654 *
24655 * - keys are validations tokens (error names).
24656 * - values are arrays of controls or forms that have a pending validator for the given error name.
24657 *
24658 * See {@link form.FormController#$error $error} for a list of built-in validation tokens.
24659 *
24660 * @property {Object} $error An object hash, containing references to controls or forms with failing
24661 * validators, where:
24662 *
24663 * - keys are validation tokens (error names),
24664 * - values are arrays of controls or forms that have a failing validator for the given error name.
24665 *
24666 * Built-in validation tokens:
24667 * - `email`
24668 * - `max`
24669 * - `maxlength`
24670 * - `min`
24671 * - `minlength`
24672 * - `number`
24673 * - `pattern`
24674 * - `required`
24675 * - `url`
24676 * - `date`
24677 * - `datetimelocal`
24678 * - `time`
24679 * - `week`
24680 * - `month`
24681 *
24682 * @description
24683 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
24684 * such as being valid/invalid or dirty/pristine.
24685 *
24686 * Each {@link ng.directive:form form} directive creates an instance
24687 * of `FormController`.
24688 *
24689 */
24690//asks for $scope to fool the BC controller module
24691FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
24692function FormController($element, $attrs, $scope, $animate, $interpolate) {
24693 this.$$controls = [];
24694
24695 // init state
24696 this.$error = {};
24697 this.$$success = {};
24698 this.$pending = undefined;
24699 this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope);
24700 this.$dirty = false;
24701 this.$pristine = true;
24702 this.$valid = true;
24703 this.$invalid = false;
24704 this.$submitted = false;
24705 this.$$parentForm = nullFormCtrl;
24706
24707 this.$$element = $element;
24708 this.$$animate = $animate;
24709
24710 setupValidity(this);
24711}
24712
24713FormController.prototype = {
24714 /**
24715 * @ngdoc method
24716 * @name form.FormController#$rollbackViewValue
24717 *
24718 * @description
24719 * Rollback all form controls pending updates to the `$modelValue`.
24720 *
24721 * Updates may be pending by a debounced event or because the input is waiting for a some future
24722 * event defined in `ng-model-options`. This method is typically needed by the reset button of
24723 * a form that uses `ng-model-options` to pend updates.
24724 */
24725 $rollbackViewValue: function() {
24726 forEach(this.$$controls, function(control) {
24727 control.$rollbackViewValue();
24728 });
24729 },
24730
24731 /**
24732 * @ngdoc method
24733 * @name form.FormController#$commitViewValue
24734 *
24735 * @description
24736 * Commit all form controls pending updates to the `$modelValue`.
24737 *
24738 * Updates may be pending by a debounced event or because the input is waiting for a some future
24739 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
24740 * usually handles calling this in response to input events.
24741 */
24742 $commitViewValue: function() {
24743 forEach(this.$$controls, function(control) {
24744 control.$commitViewValue();
24745 });
24746 },
24747
24748 /**
24749 * @ngdoc method
24750 * @name form.FormController#$addControl
24751 * @param {object} control control object, either a {@link form.FormController} or an
24752 * {@link ngModel.NgModelController}
24753 *
24754 * @description
24755 * Register a control with the form. Input elements using ngModelController do this automatically
24756 * when they are linked.
24757 *
24758 * Note that the current state of the control will not be reflected on the new parent form. This
24759 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
24760 * state.
24761 *
24762 * However, if the method is used programmatically, for example by adding dynamically created controls,
24763 * or controls that have been previously removed without destroying their corresponding DOM element,
24764 * it's the developers responsibility to make sure the current state propagates to the parent form.
24765 *
24766 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
24767 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
24768 */
24769 $addControl: function(control) {
24770 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
24771 // and not added to the scope. Now we throw an error.
24772 assertNotHasOwnProperty(control.$name, 'input');
24773 this.$$controls.push(control);
24774
24775 if (control.$name) {
24776 this[control.$name] = control;
24777 }
24778
24779 control.$$parentForm = this;
24780 },
24781
24782 /**
24783 * @ngdoc method
24784 * @name form.FormController#$getControls
24785 * @returns {Array} the controls that are currently part of this form
24786 *
24787 * @description
24788 * This method returns a **shallow copy** of the controls that are currently part of this form.
24789 * The controls can be instances of {@link form.FormController `FormController`}
24790 * ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}.
24791 * If you need access to the controls of child-forms, you have to call `$getControls()`
24792 * recursively on them.
24793 * This can be used for example to iterate over all controls to validate them.
24794 *
24795 * The controls can be accessed normally, but adding to, or removing controls from the array has
24796 * no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and
24797 * {@link form.FormController#$removeControl `$removeControl()`} for this use-case.
24798 * Likewise, adding a control to, or removing a control from the form is not reflected
24799 * in the shallow copy. That means you should get a fresh copy from `$getControls()` every time
24800 * you need access to the controls.
24801 */
24802 $getControls: function() {
24803 return shallowCopy(this.$$controls);
24804 },
24805
24806 // Private API: rename a form control
24807 $$renameControl: function(control, newName) {
24808 var oldName = control.$name;
24809
24810 if (this[oldName] === control) {
24811 delete this[oldName];
24812 }
24813 this[newName] = control;
24814 control.$name = newName;
24815 },
24816
24817 /**
24818 * @ngdoc method
24819 * @name form.FormController#$removeControl
24820 * @param {object} control control object, either a {@link form.FormController} or an
24821 * {@link ngModel.NgModelController}
24822 *
24823 * @description
24824 * Deregister a control from the form.
24825 *
24826 * Input elements using ngModelController do this automatically when they are destroyed.
24827 *
24828 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
24829 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
24830 * different from case to case. For example, removing the only `$dirty` control from a form may or
24831 * may not mean that the form is still `$dirty`.
24832 */
24833 $removeControl: function(control) {
24834 if (control.$name && this[control.$name] === control) {
24835 delete this[control.$name];
24836 }
24837 forEach(this.$pending, function(value, name) {
24838 // eslint-disable-next-line no-invalid-this
24839 this.$setValidity(name, null, control);
24840 }, this);
24841 forEach(this.$error, function(value, name) {
24842 // eslint-disable-next-line no-invalid-this
24843 this.$setValidity(name, null, control);
24844 }, this);
24845 forEach(this.$$success, function(value, name) {
24846 // eslint-disable-next-line no-invalid-this
24847 this.$setValidity(name, null, control);
24848 }, this);
24849
24850 arrayRemove(this.$$controls, control);
24851 control.$$parentForm = nullFormCtrl;
24852 },
24853
24854 /**
24855 * @ngdoc method
24856 * @name form.FormController#$setDirty
24857 *
24858 * @description
24859 * Sets the form to a dirty state.
24860 *
24861 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
24862 * state (ng-dirty class). This method will also propagate to parent forms.
24863 */
24864 $setDirty: function() {
24865 this.$$animate.removeClass(this.$$element, PRISTINE_CLASS);
24866 this.$$animate.addClass(this.$$element, DIRTY_CLASS);
24867 this.$dirty = true;
24868 this.$pristine = false;
24869 this.$$parentForm.$setDirty();
24870 },
24871
24872 /**
24873 * @ngdoc method
24874 * @name form.FormController#$setPristine
24875 *
24876 * @description
24877 * Sets the form to its pristine state.
24878 *
24879 * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes
24880 * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted`
24881 * state to false.
24882 *
24883 * This method will also propagate to all the controls contained in this form.
24884 *
24885 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
24886 * saving or resetting it.
24887 */
24888 $setPristine: function() {
24889 this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
24890 this.$dirty = false;
24891 this.$pristine = true;
24892 this.$submitted = false;
24893 forEach(this.$$controls, function(control) {
24894 control.$setPristine();
24895 });
24896 },
24897
24898 /**
24899 * @ngdoc method
24900 * @name form.FormController#$setUntouched
24901 *
24902 * @description
24903 * Sets the form to its untouched state.
24904 *
24905 * This method can be called to remove the 'ng-touched' class and set the form controls to their
24906 * untouched state (ng-untouched class).
24907 *
24908 * Setting a form controls back to their untouched state is often useful when setting the form
24909 * back to its pristine state.
24910 */
24911 $setUntouched: function() {
24912 forEach(this.$$controls, function(control) {
24913 control.$setUntouched();
24914 });
24915 },
24916
24917 /**
24918 * @ngdoc method
24919 * @name form.FormController#$setSubmitted
24920 *
24921 * @description
24922 * Sets the form to its `$submitted` state. This will also set `$submitted` on all child and
24923 * parent forms of the form.
24924 */
24925 $setSubmitted: function() {
24926 var rootForm = this;
24927 while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) {
24928 rootForm = rootForm.$$parentForm;
24929 }
24930 rootForm.$$setSubmitted();
24931 },
24932
24933 $$setSubmitted: function() {
24934 this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
24935 this.$submitted = true;
24936 forEach(this.$$controls, function(control) {
24937 if (control.$$setSubmitted) {
24938 control.$$setSubmitted();
24939 }
24940 });
24941 }
24942};
24943
24944/**
24945 * @ngdoc method
24946 * @name form.FormController#$setValidity
24947 *
24948 * @description
24949 * Change the validity state of the form, and notify the parent form (if any).
24950 *
24951 * Application developers will rarely need to call this method directly. It is used internally, by
24952 * {@link ngModel.NgModelController#$setValidity NgModelController.$setValidity()}, to propagate a
24953 * control's validity state to the parent `FormController`.
24954 *
24955 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be
24956 * assigned to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` (for
24957 * unfulfilled `$asyncValidators`), so that it is available for data-binding. The
24958 * `validationErrorKey` should be in camelCase and will get converted into dash-case for
24959 * class name. Example: `myError` will result in `ng-valid-my-error` and
24960 * `ng-invalid-my-error` classes and can be bound to as `{{ someForm.$error.myError }}`.
24961 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending
24962 * (undefined), or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
24963 * Skipped is used by AngularJS when validators do not run because of parse errors and when
24964 * `$asyncValidators` do not run because any of the `$validators` failed.
24965 * @param {NgModelController | FormController} controller - The controller whose validity state is
24966 * triggering the change.
24967 */
24968addSetValidityMethod({
24969 clazz: FormController,
24970 set: function(object, property, controller) {
24971 var list = object[property];
24972 if (!list) {
24973 object[property] = [controller];
24974 } else {
24975 var index = list.indexOf(controller);
24976 if (index === -1) {
24977 list.push(controller);
24978 }
24979 }
24980 },
24981 unset: function(object, property, controller) {
24982 var list = object[property];
24983 if (!list) {
24984 return;
24985 }
24986 arrayRemove(list, controller);
24987 if (list.length === 0) {
24988 delete object[property];
24989 }
24990 }
24991});
24992
24993/**
24994 * @ngdoc directive
24995 * @name ngForm
24996 * @restrict EAC
24997 *
24998 * @description
24999 * Helper directive that makes it possible to create control groups inside a
25000 * {@link ng.directive:form `form`} directive.
25001 * These "child forms" can be used, for example, to determine the validity of a sub-group of
25002 * controls.
25003 *
25004 * <div class="alert alert-danger">
25005 * **Note**: `ngForm` cannot be used as a replacement for `<form>`, because it lacks its
25006 * [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element).
25007 * Specifically, you cannot submit `ngForm` like a `<form>` tag. That means,
25008 * you cannot send data to the server with `ngForm`, or integrate it with
25009 * {@link ng.directive:ngSubmit `ngSubmit`}.
25010 * </div>
25011 *
25012 * @param {string=} ngForm|name Name of the form. If specified, the form controller will
25013 * be published into the related scope, under this name.
25014 *
25015 */
25016
25017 /**
25018 * @ngdoc directive
25019 * @name form
25020 * @restrict E
25021 *
25022 * @description
25023 * Directive that instantiates
25024 * {@link form.FormController FormController}.
25025 *
25026 * If the `name` attribute is specified, the form controller is published onto the current scope under
25027 * this name.
25028 *
25029 * ## Alias: {@link ng.directive:ngForm `ngForm`}
25030 *
25031 * In AngularJS, forms can be nested. This means that the outer form is valid when all of the child
25032 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
25033 * AngularJS provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to
25034 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group
25035 * of controls needs to be determined.
25036 *
25037 * ## CSS classes
25038 * - `ng-valid` is set if the form is valid.
25039 * - `ng-invalid` is set if the form is invalid.
25040 * - `ng-pending` is set if the form is pending.
25041 * - `ng-pristine` is set if the form is pristine.
25042 * - `ng-dirty` is set if the form is dirty.
25043 * - `ng-submitted` is set if the form was submitted.
25044 *
25045 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25046 *
25047 *
25048 * ## Submitting a form and preventing the default action
25049 *
25050 * Since the role of forms in client-side AngularJS applications is different than in classical
25051 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
25052 * page reload that sends the data to the server. Instead some javascript logic should be triggered
25053 * to handle the form submission in an application-specific way.
25054 *
25055 * For this reason, AngularJS prevents the default action (form submission to the server) unless the
25056 * `<form>` element has an `action` attribute specified.
25057 *
25058 * You can use one of the following two ways to specify what javascript method should be called when
25059 * a form is submitted:
25060 *
25061 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
25062 * - {@link ng.directive:ngClick ngClick} directive on the first
25063 * button or input field of type submit (input[type=submit])
25064 *
25065 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
25066 * or {@link ng.directive:ngClick ngClick} directives.
25067 * This is because of the following form submission rules in the HTML specification:
25068 *
25069 * - If a form has only one input field then hitting enter in this field triggers form submit
25070 * (`ngSubmit`)
25071 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
25072 * doesn't trigger submit
25073 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
25074 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
25075 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
25076 *
25077 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
25078 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25079 * to have access to the updated model.
25080 *
25081 * @animations
25082 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
25083 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
25084 * other validations that are performed within the form. Animations in ngForm are similar to how
25085 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
25086 * as JS animations.
25087 *
25088 * The following example shows a simple way to utilize CSS transitions to style a form element
25089 * that has been rendered as invalid after it has been validated:
25090 *
25091 * <pre>
25092 * //be sure to include ngAnimate as a module to hook into more
25093 * //advanced animations
25094 * .my-form {
25095 * transition:0.5s linear all;
25096 * background: white;
25097 * }
25098 * .my-form.ng-invalid {
25099 * background: red;
25100 * color:white;
25101 * }
25102 * </pre>
25103 *
25104 * @example
25105 <example name="ng-form" deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
25106 <file name="index.html">
25107 <script>
25108 angular.module('formExample', [])
25109 .controller('FormController', ['$scope', function($scope) {
25110 $scope.userType = 'guest';
25111 }]);
25112 </script>
25113 <style>
25114 .my-form {
25115 transition:all linear 0.5s;
25116 background: transparent;
25117 }
25118 .my-form.ng-invalid {
25119 background: red;
25120 }
25121 </style>
25122 <form name="myForm" ng-controller="FormController" class="my-form">
25123 userType: <input name="input" ng-model="userType" required>
25124 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
25125 <code>userType = {{userType}}</code><br>
25126 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
25127 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
25128 <code>myForm.$valid = {{myForm.$valid}}</code><br>
25129 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
25130 </form>
25131 </file>
25132 <file name="protractor.js" type="protractor">
25133 it('should initialize to model', function() {
25134 var userType = element(by.binding('userType'));
25135 var valid = element(by.binding('myForm.input.$valid'));
25136
25137 expect(userType.getText()).toContain('guest');
25138 expect(valid.getText()).toContain('true');
25139 });
25140
25141 it('should be invalid if empty', function() {
25142 var userType = element(by.binding('userType'));
25143 var valid = element(by.binding('myForm.input.$valid'));
25144 var userInput = element(by.model('userType'));
25145
25146 userInput.clear();
25147 userInput.sendKeys('');
25148
25149 expect(userType.getText()).toEqual('userType =');
25150 expect(valid.getText()).toContain('false');
25151 });
25152 </file>
25153 </example>
25154 *
25155 * @param {string=} name Name of the form. If specified, the form controller will be published into
25156 * related scope, under this name.
25157 */
25158var formDirectiveFactory = function(isNgForm) {
25159 return ['$timeout', '$parse', function($timeout, $parse) {
25160 var formDirective = {
25161 name: 'form',
25162 restrict: isNgForm ? 'EAC' : 'E',
25163 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
25164 controller: FormController,
25165 compile: function ngFormCompile(formElement, attr) {
25166 // Setup initial state of the control
25167 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
25168
25169 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
25170
25171 return {
25172 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
25173 var controller = ctrls[0];
25174
25175 // if `action` attr is not present on the form, prevent the default action (submission)
25176 if (!('action' in attr)) {
25177 // we can't use jq events because if a form is destroyed during submission the default
25178 // action is not prevented. see #1238
25179 //
25180 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
25181 // page reload if the form was destroyed by submission of the form via a click handler
25182 // on a button in the form. Looks like an IE9 specific bug.
25183 var handleFormSubmission = function(event) {
25184 scope.$apply(function() {
25185 controller.$commitViewValue();
25186 controller.$setSubmitted();
25187 });
25188
25189 event.preventDefault();
25190 };
25191
25192 formElement[0].addEventListener('submit', handleFormSubmission);
25193
25194 // unregister the preventDefault listener so that we don't not leak memory but in a
25195 // way that will achieve the prevention of the default action.
25196 formElement.on('$destroy', function() {
25197 $timeout(function() {
25198 formElement[0].removeEventListener('submit', handleFormSubmission);
25199 }, 0, false);
25200 });
25201 }
25202
25203 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
25204 parentFormCtrl.$addControl(controller);
25205
25206 var setter = nameAttr ? getSetter(controller.$name) : noop;
25207
25208 if (nameAttr) {
25209 setter(scope, controller);
25210 attr.$observe(nameAttr, function(newValue) {
25211 if (controller.$name === newValue) return;
25212 setter(scope, undefined);
25213 controller.$$parentForm.$$renameControl(controller, newValue);
25214 setter = getSetter(controller.$name);
25215 setter(scope, controller);
25216 });
25217 }
25218 formElement.on('$destroy', function() {
25219 controller.$$parentForm.$removeControl(controller);
25220 setter(scope, undefined);
25221 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
25222 });
25223 }
25224 };
25225 }
25226 };
25227
25228 return formDirective;
25229
25230 function getSetter(expression) {
25231 if (expression === '') {
25232 //create an assignable expression, so forms with an empty name can be renamed later
25233 return $parse('this[""]').assign;
25234 }
25235 return $parse(expression).assign || noop;
25236 }
25237 }];
25238};
25239
25240var formDirective = formDirectiveFactory();
25241var ngFormDirective = formDirectiveFactory(true);
25242
25243
25244
25245// helper methods
25246function setupValidity(instance) {
25247 instance.$$classCache = {};
25248 instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS));
25249}
25250function addSetValidityMethod(context) {
25251 var clazz = context.clazz,
25252 set = context.set,
25253 unset = context.unset;
25254
25255 clazz.prototype.$setValidity = function(validationErrorKey, state, controller) {
25256 if (isUndefined(state)) {
25257 createAndSet(this, '$pending', validationErrorKey, controller);
25258 } else {
25259 unsetAndCleanup(this, '$pending', validationErrorKey, controller);
25260 }
25261 if (!isBoolean(state)) {
25262 unset(this.$error, validationErrorKey, controller);
25263 unset(this.$$success, validationErrorKey, controller);
25264 } else {
25265 if (state) {
25266 unset(this.$error, validationErrorKey, controller);
25267 set(this.$$success, validationErrorKey, controller);
25268 } else {
25269 set(this.$error, validationErrorKey, controller);
25270 unset(this.$$success, validationErrorKey, controller);
25271 }
25272 }
25273 if (this.$pending) {
25274 cachedToggleClass(this, PENDING_CLASS, true);
25275 this.$valid = this.$invalid = undefined;
25276 toggleValidationCss(this, '', null);
25277 } else {
25278 cachedToggleClass(this, PENDING_CLASS, false);
25279 this.$valid = isObjectEmpty(this.$error);
25280 this.$invalid = !this.$valid;
25281 toggleValidationCss(this, '', this.$valid);
25282 }
25283
25284 // re-read the state as the set/unset methods could have
25285 // combined state in this.$error[validationError] (used for forms),
25286 // where setting/unsetting only increments/decrements the value,
25287 // and does not replace it.
25288 var combinedState;
25289 if (this.$pending && this.$pending[validationErrorKey]) {
25290 combinedState = undefined;
25291 } else if (this.$error[validationErrorKey]) {
25292 combinedState = false;
25293 } else if (this.$$success[validationErrorKey]) {
25294 combinedState = true;
25295 } else {
25296 combinedState = null;
25297 }
25298
25299 toggleValidationCss(this, validationErrorKey, combinedState);
25300 this.$$parentForm.$setValidity(validationErrorKey, combinedState, this);
25301 };
25302
25303 function createAndSet(ctrl, name, value, controller) {
25304 if (!ctrl[name]) {
25305 ctrl[name] = {};
25306 }
25307 set(ctrl[name], value, controller);
25308 }
25309
25310 function unsetAndCleanup(ctrl, name, value, controller) {
25311 if (ctrl[name]) {
25312 unset(ctrl[name], value, controller);
25313 }
25314 if (isObjectEmpty(ctrl[name])) {
25315 ctrl[name] = undefined;
25316 }
25317 }
25318
25319 function cachedToggleClass(ctrl, className, switchValue) {
25320 if (switchValue && !ctrl.$$classCache[className]) {
25321 ctrl.$$animate.addClass(ctrl.$$element, className);
25322 ctrl.$$classCache[className] = true;
25323 } else if (!switchValue && ctrl.$$classCache[className]) {
25324 ctrl.$$animate.removeClass(ctrl.$$element, className);
25325 ctrl.$$classCache[className] = false;
25326 }
25327 }
25328
25329 function toggleValidationCss(ctrl, validationErrorKey, isValid) {
25330 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
25331
25332 cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true);
25333 cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false);
25334 }
25335}
25336
25337function isObjectEmpty(obj) {
25338 if (obj) {
25339 for (var prop in obj) {
25340 if (obj.hasOwnProperty(prop)) {
25341 return false;
25342 }
25343 }
25344 }
25345 return true;
25346}
25347
25348/* global
25349 VALID_CLASS: false,
25350 INVALID_CLASS: false,
25351 PRISTINE_CLASS: false,
25352 DIRTY_CLASS: false,
25353 ngModelMinErr: false
25354*/
25355
25356// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
25357var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/;
25358// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
25359// Note: We are being more lenient, because browsers are too.
25360// 1. Scheme
25361// 2. Slashes
25362// 3. Username
25363// 4. Password
25364// 5. Hostname
25365// 6. Port
25366// 7. Path
25367// 8. Query
25368// 9. Fragment
25369// 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999
25370var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
25371// eslint-disable-next-line max-len
25372var EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
25373var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
25374var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/;
25375var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
25376var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/;
25377var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/;
25378var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
25379
25380var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown';
25381var PARTIAL_VALIDATION_TYPES = createMap();
25382forEach('date,datetime-local,month,time,week'.split(','), function(type) {
25383 PARTIAL_VALIDATION_TYPES[type] = true;
25384});
25385
25386var inputType = {
25387
25388 /**
25389 * @ngdoc input
25390 * @name input[text]
25391 *
25392 * @description
25393 * Standard HTML text input with AngularJS data binding, inherited by most of the `input` elements.
25394 *
25395 *
25396 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
25397 * @param {string=} name Property name of the form under which the control is published.
25398 * @param {string=} required Adds `required` validation error key if the value is not entered.
25399 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
25400 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
25401 * `required` when you want to data-bind to the `required` attribute.
25402 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
25403 * minlength.
25404 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
25405 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
25406 * any length.
25407 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
25408 * that contains the regular expression body that will be converted to a regular expression
25409 * as in the ngPattern directive.
25410 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
25411 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
25412 * If the expression evaluates to a RegExp object, then this is used directly.
25413 * If the expression evaluates to a string, then it will be converted to a RegExp
25414 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
25415 * `new RegExp('^abc$')`.<br />
25416 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
25417 * start at the index of the last search's match, thus not taking the whole input value into
25418 * account.
25419 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
25420 * interaction with the input element.
25421 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
25422 * This parameter is ignored for input[type=password] controls, which will never trim the
25423 * input.
25424 *
25425 * @example
25426 <example name="text-input-directive" module="textInputExample">
25427 <file name="index.html">
25428 <script>
25429 angular.module('textInputExample', [])
25430 .controller('ExampleController', ['$scope', function($scope) {
25431 $scope.example = {
25432 text: 'guest',
25433 word: /^\s*\w*\s*$/
25434 };
25435 }]);
25436 </script>
25437 <form name="myForm" ng-controller="ExampleController">
25438 <label>Single word:
25439 <input type="text" name="input" ng-model="example.text"
25440 ng-pattern="example.word" required ng-trim="false">
25441 </label>
25442 <div role="alert">
25443 <span class="error" ng-show="myForm.input.$error.required">
25444 Required!</span>
25445 <span class="error" ng-show="myForm.input.$error.pattern">
25446 Single word only!</span>
25447 </div>
25448 <code>text = {{example.text}}</code><br/>
25449 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br/>
25450 <code>myForm.input.$error = {{myForm.input.$error}}</code><br/>
25451 <code>myForm.$valid = {{myForm.$valid}}</code><br/>
25452 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br/>
25453 </form>
25454 </file>
25455 <file name="protractor.js" type="protractor">
25456 var text = element(by.binding('example.text'));
25457 var valid = element(by.binding('myForm.input.$valid'));
25458 var input = element(by.model('example.text'));
25459
25460 it('should initialize to model', function() {
25461 expect(text.getText()).toContain('guest');
25462 expect(valid.getText()).toContain('true');
25463 });
25464
25465 it('should be invalid if empty', function() {
25466 input.clear();
25467 input.sendKeys('');
25468
25469 expect(text.getText()).toEqual('text =');
25470 expect(valid.getText()).toContain('false');
25471 });
25472
25473 it('should be invalid if multi word', function() {
25474 input.clear();
25475 input.sendKeys('hello world');
25476
25477 expect(valid.getText()).toContain('false');
25478 });
25479 </file>
25480 </example>
25481 */
25482 'text': textInputType,
25483
25484 /**
25485 * @ngdoc input
25486 * @name input[date]
25487 *
25488 * @description
25489 * Input with date validation and transformation. In browsers that do not yet support
25490 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
25491 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
25492 * modern browsers do not yet support this input type, it is important to provide cues to users on the
25493 * expected input format via a placeholder or label.
25494 *
25495 * The model must always be a Date object, otherwise AngularJS will throw an error.
25496 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
25497 *
25498 * The timezone to be used to read/write the `Date` instance in the model can be defined using
25499 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
25500 *
25501 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
25502 * @param {string=} name Property name of the form under which the control is published.
25503 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
25504 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
25505 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
25506 * constraint validation.
25507 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
25508 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
25509 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
25510 * constraint validation.
25511 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
25512 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
25513 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
25514 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
25515 * @param {string=} required Sets `required` validation error key if the value is not entered.
25516 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
25517 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
25518 * `required` when you want to data-bind to the `required` attribute.
25519 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
25520 * interaction with the input element.
25521 *
25522 * @example
25523 <example name="date-input-directive" module="dateInputExample">
25524 <file name="index.html">
25525 <script>
25526 angular.module('dateInputExample', [])
25527 .controller('DateController', ['$scope', function($scope) {
25528 $scope.example = {
25529 value: new Date(2013, 9, 22)
25530 };
25531 }]);
25532 </script>
25533 <form name="myForm" ng-controller="DateController as dateCtrl">
25534 <label for="exampleInput">Pick a date in 2013:</label>
25535 <input type="date" id="exampleInput" name="input" ng-model="example.value"
25536 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
25537 <div role="alert">
25538 <span class="error" ng-show="myForm.input.$error.required">
25539 Required!</span>
25540 <span class="error" ng-show="myForm.input.$error.date">
25541 Not a valid date!</span>
25542 </div>
25543 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
25544 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
25545 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
25546 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
25547 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25548 </form>
25549 </file>
25550 <file name="protractor.js" type="protractor">
25551 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
25552 var valid = element(by.binding('myForm.input.$valid'));
25553
25554 // currently protractor/webdriver does not support
25555 // sending keys to all known HTML5 input controls
25556 // for various browsers (see https://github.com/angular/protractor/issues/562).
25557 function setInput(val) {
25558 // set the value of the element and force validation.
25559 var scr = "var ipt = document.getElementById('exampleInput'); " +
25560 "ipt.value = '" + val + "';" +
25561 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
25562 browser.executeScript(scr);
25563 }
25564
25565 it('should initialize to model', function() {
25566 expect(value.getText()).toContain('2013-10-22');
25567 expect(valid.getText()).toContain('myForm.input.$valid = true');
25568 });
25569
25570 it('should be invalid if empty', function() {
25571 setInput('');
25572 expect(value.getText()).toEqual('value =');
25573 expect(valid.getText()).toContain('myForm.input.$valid = false');
25574 });
25575
25576 it('should be invalid if over max', function() {
25577 setInput('2015-01-01');
25578 expect(value.getText()).toContain('');
25579 expect(valid.getText()).toContain('myForm.input.$valid = false');
25580 });
25581 </file>
25582 </example>
25583 */
25584 'date': createDateInputType('date', DATE_REGEXP,
25585 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
25586 'yyyy-MM-dd'),
25587
25588 /**
25589 * @ngdoc input
25590 * @name input[datetime-local]
25591 *
25592 * @description
25593 * Input with datetime validation and transformation. In browsers that do not yet support
25594 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
25595 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
25596 *
25597 * The model must always be a Date object, otherwise AngularJS will throw an error.
25598 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
25599 *
25600 * The timezone to be used to read/write the `Date` instance in the model can be defined using
25601 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
25602 *
25603 * The format of the displayed time can be adjusted with the
25604 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
25605 * and `timeStripZeroSeconds`.
25606 *
25607 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
25608 * @param {string=} name Property name of the form under which the control is published.
25609 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
25610 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
25611 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
25612 * Note that `min` will also add native HTML5 constraint validation.
25613 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
25614 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
25615 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
25616 * Note that `max` will also add native HTML5 constraint validation.
25617 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
25618 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
25619 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
25620 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
25621 * @param {string=} required Sets `required` validation error key if the value is not entered.
25622 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
25623 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
25624 * `required` when you want to data-bind to the `required` attribute.
25625 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
25626 * interaction with the input element.
25627 *
25628 * @example
25629 <example name="datetimelocal-input-directive" module="dateExample">
25630 <file name="index.html">
25631 <script>
25632 angular.module('dateExample', [])
25633 .controller('DateController', ['$scope', function($scope) {
25634 $scope.example = {
25635 value: new Date(2010, 11, 28, 14, 57)
25636 };
25637 }]);
25638 </script>
25639 <form name="myForm" ng-controller="DateController as dateCtrl">
25640 <label for="exampleInput">Pick a date between in 2013:</label>
25641 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
25642 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
25643 <div role="alert">
25644 <span class="error" ng-show="myForm.input.$error.required">
25645 Required!</span>
25646 <span class="error" ng-show="myForm.input.$error.datetimelocal">
25647 Not a valid date!</span>
25648 </div>
25649 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
25650 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
25651 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
25652 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
25653 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25654 </form>
25655 </file>
25656 <file name="protractor.js" type="protractor">
25657 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
25658 var valid = element(by.binding('myForm.input.$valid'));
25659
25660 // currently protractor/webdriver does not support
25661 // sending keys to all known HTML5 input controls
25662 // for various browsers (https://github.com/angular/protractor/issues/562).
25663 function setInput(val) {
25664 // set the value of the element and force validation.
25665 var scr = "var ipt = document.getElementById('exampleInput'); " +
25666 "ipt.value = '" + val + "';" +
25667 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
25668 browser.executeScript(scr);
25669 }
25670
25671 it('should initialize to model', function() {
25672 expect(value.getText()).toContain('2010-12-28T14:57:00');
25673 expect(valid.getText()).toContain('myForm.input.$valid = true');
25674 });
25675
25676 it('should be invalid if empty', function() {
25677 setInput('');
25678 expect(value.getText()).toEqual('value =');
25679 expect(valid.getText()).toContain('myForm.input.$valid = false');
25680 });
25681
25682 it('should be invalid if over max', function() {
25683 setInput('2015-01-01T23:59:00');
25684 expect(value.getText()).toContain('');
25685 expect(valid.getText()).toContain('myForm.input.$valid = false');
25686 });
25687 </file>
25688 </example>
25689 */
25690 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
25691 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
25692 'yyyy-MM-ddTHH:mm:ss.sss'),
25693
25694 /**
25695 * @ngdoc input
25696 * @name input[time]
25697 *
25698 * @description
25699 * Input with time validation and transformation. In browsers that do not yet support
25700 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
25701 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
25702 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
25703 *
25704 * The model must always be a Date object, otherwise AngularJS will throw an error.
25705 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
25706 *
25707 * The timezone to be used to read/write the `Date` instance in the model can be defined using
25708 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default,
25709 * this is the timezone of the browser.
25710 *
25711 * The format of the displayed time can be adjusted with the
25712 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
25713 * and `timeStripZeroSeconds`.
25714 *
25715 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
25716 * @param {string=} name Property name of the form under which the control is published.
25717 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
25718 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
25719 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
25720 * native HTML5 constraint validation.
25721 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
25722 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
25723 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
25724 * native HTML5 constraint validation.
25725 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
25726 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
25727 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
25728 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
25729 * @param {string=} required Sets `required` validation error key if the value is not entered.
25730 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
25731 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
25732 * `required` when you want to data-bind to the `required` attribute.
25733 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
25734 * interaction with the input element.
25735 *
25736 * @example
25737 <example name="time-input-directive" module="timeExample">
25738 <file name="index.html">
25739 <script>
25740 angular.module('timeExample', [])
25741 .controller('DateController', ['$scope', function($scope) {
25742 $scope.example = {
25743 value: new Date(1970, 0, 1, 14, 57, 0)
25744 };
25745 }]);
25746 </script>
25747 <form name="myForm" ng-controller="DateController as dateCtrl">
25748 <label for="exampleInput">Pick a time between 8am and 5pm:</label>
25749 <input type="time" id="exampleInput" name="input" ng-model="example.value"
25750 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
25751 <div role="alert">
25752 <span class="error" ng-show="myForm.input.$error.required">
25753 Required!</span>
25754 <span class="error" ng-show="myForm.input.$error.time">
25755 Not a valid date!</span>
25756 </div>
25757 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
25758 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
25759 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
25760 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
25761 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25762 </form>
25763 </file>
25764 <file name="protractor.js" type="protractor">
25765 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
25766 var valid = element(by.binding('myForm.input.$valid'));
25767
25768 // currently protractor/webdriver does not support
25769 // sending keys to all known HTML5 input controls
25770 // for various browsers (https://github.com/angular/protractor/issues/562).
25771 function setInput(val) {
25772 // set the value of the element and force validation.
25773 var scr = "var ipt = document.getElementById('exampleInput'); " +
25774 "ipt.value = '" + val + "';" +
25775 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
25776 browser.executeScript(scr);
25777 }
25778
25779 it('should initialize to model', function() {
25780 expect(value.getText()).toContain('14:57:00');
25781 expect(valid.getText()).toContain('myForm.input.$valid = true');
25782 });
25783
25784 it('should be invalid if empty', function() {
25785 setInput('');
25786 expect(value.getText()).toEqual('value =');
25787 expect(valid.getText()).toContain('myForm.input.$valid = false');
25788 });
25789
25790 it('should be invalid if over max', function() {
25791 setInput('23:59:00');
25792 expect(value.getText()).toContain('');
25793 expect(valid.getText()).toContain('myForm.input.$valid = false');
25794 });
25795 </file>
25796 </example>
25797 */
25798 'time': createDateInputType('time', TIME_REGEXP,
25799 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
25800 'HH:mm:ss.sss'),
25801
25802 /**
25803 * @ngdoc input
25804 * @name input[week]
25805 *
25806 * @description
25807 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
25808 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
25809 * week format (yyyy-W##), for example: `2013-W02`.
25810 *
25811 * The model must always be a Date object, otherwise AngularJS will throw an error.
25812 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
25813 *
25814 * The value of the resulting Date object will be set to Thursday at 00:00:00 of the requested week,
25815 * due to ISO-8601 week numbering standards. Information on ISO's system for numbering the weeks of the
25816 * year can be found at: https://en.wikipedia.org/wiki/ISO_8601#Week_dates
25817 *
25818 * The timezone to be used to read/write the `Date` instance in the model can be defined using
25819 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
25820 *
25821 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
25822 * @param {string=} name Property name of the form under which the control is published.
25823 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
25824 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
25825 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
25826 * native HTML5 constraint validation.
25827 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
25828 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
25829 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
25830 * native HTML5 constraint validation.
25831 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
25832 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
25833 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
25834 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
25835 * @param {string=} required Sets `required` validation error key if the value is not entered.
25836 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
25837 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
25838 * `required` when you want to data-bind to the `required` attribute.
25839 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
25840 * interaction with the input element.
25841 *
25842 * @example
25843 <example name="week-input-directive" module="weekExample">
25844 <file name="index.html">
25845 <script>
25846 angular.module('weekExample', [])
25847 .controller('DateController', ['$scope', function($scope) {
25848 $scope.example = {
25849 value: new Date(2013, 0, 3)
25850 };
25851 }]);
25852 </script>
25853 <form name="myForm" ng-controller="DateController as dateCtrl">
25854 <label>Pick a date between in 2013:
25855 <input id="exampleInput" type="week" name="input" ng-model="example.value"
25856 placeholder="YYYY-W##" min="2012-W32"
25857 max="2013-W52" required />
25858 </label>
25859 <div role="alert">
25860 <span class="error" ng-show="myForm.input.$error.required">
25861 Required!</span>
25862 <span class="error" ng-show="myForm.input.$error.week">
25863 Not a valid date!</span>
25864 </div>
25865 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
25866 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
25867 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
25868 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
25869 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25870 </form>
25871 </file>
25872 <file name="protractor.js" type="protractor">
25873 var value = element(by.binding('example.value | date: "yyyy-Www"'));
25874 var valid = element(by.binding('myForm.input.$valid'));
25875
25876 // currently protractor/webdriver does not support
25877 // sending keys to all known HTML5 input controls
25878 // for various browsers (https://github.com/angular/protractor/issues/562).
25879 function setInput(val) {
25880 // set the value of the element and force validation.
25881 var scr = "var ipt = document.getElementById('exampleInput'); " +
25882 "ipt.value = '" + val + "';" +
25883 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
25884 browser.executeScript(scr);
25885 }
25886
25887 it('should initialize to model', function() {
25888 expect(value.getText()).toContain('2013-W01');
25889 expect(valid.getText()).toContain('myForm.input.$valid = true');
25890 });
25891
25892 it('should be invalid if empty', function() {
25893 setInput('');
25894 expect(value.getText()).toEqual('value =');
25895 expect(valid.getText()).toContain('myForm.input.$valid = false');
25896 });
25897
25898 it('should be invalid if over max', function() {
25899 setInput('2015-W01');
25900 expect(value.getText()).toContain('');
25901 expect(valid.getText()).toContain('myForm.input.$valid = false');
25902 });
25903 </file>
25904 </example>
25905 */
25906 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
25907
25908 /**
25909 * @ngdoc input
25910 * @name input[month]
25911 *
25912 * @description
25913 * Input with month validation and transformation. In browsers that do not yet support
25914 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
25915 * month format (yyyy-MM), for example: `2009-01`.
25916 *
25917 * The model must always be a Date object, otherwise AngularJS will throw an error.
25918 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
25919 * If the model is not set to the first of the month, the next view to model update will set it
25920 * to the first of the month.
25921 *
25922 * The timezone to be used to read/write the `Date` instance in the model can be defined using
25923 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
25924 *
25925 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
25926 * @param {string=} name Property name of the form under which the control is published.
25927 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
25928 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
25929 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
25930 * native HTML5 constraint validation.
25931 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
25932 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
25933 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
25934 * native HTML5 constraint validation.
25935 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
25936 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
25937 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
25938 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
25939
25940 * @param {string=} required Sets `required` validation error key if the value is not entered.
25941 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
25942 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
25943 * `required` when you want to data-bind to the `required` attribute.
25944 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
25945 * interaction with the input element.
25946 *
25947 * @example
25948 <example name="month-input-directive" module="monthExample">
25949 <file name="index.html">
25950 <script>
25951 angular.module('monthExample', [])
25952 .controller('DateController', ['$scope', function($scope) {
25953 $scope.example = {
25954 value: new Date(2013, 9, 1)
25955 };
25956 }]);
25957 </script>
25958 <form name="myForm" ng-controller="DateController as dateCtrl">
25959 <label for="exampleInput">Pick a month in 2013:</label>
25960 <input id="exampleInput" type="month" name="input" ng-model="example.value"
25961 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
25962 <div role="alert">
25963 <span class="error" ng-show="myForm.input.$error.required">
25964 Required!</span>
25965 <span class="error" ng-show="myForm.input.$error.month">
25966 Not a valid month!</span>
25967 </div>
25968 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
25969 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
25970 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
25971 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
25972 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25973 </form>
25974 </file>
25975 <file name="protractor.js" type="protractor">
25976 var value = element(by.binding('example.value | date: "yyyy-MM"'));
25977 var valid = element(by.binding('myForm.input.$valid'));
25978
25979 // currently protractor/webdriver does not support
25980 // sending keys to all known HTML5 input controls
25981 // for various browsers (https://github.com/angular/protractor/issues/562).
25982 function setInput(val) {
25983 // set the value of the element and force validation.
25984 var scr = "var ipt = document.getElementById('exampleInput'); " +
25985 "ipt.value = '" + val + "';" +
25986 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
25987 browser.executeScript(scr);
25988 }
25989
25990 it('should initialize to model', function() {
25991 expect(value.getText()).toContain('2013-10');
25992 expect(valid.getText()).toContain('myForm.input.$valid = true');
25993 });
25994
25995 it('should be invalid if empty', function() {
25996 setInput('');
25997 expect(value.getText()).toEqual('value =');
25998 expect(valid.getText()).toContain('myForm.input.$valid = false');
25999 });
26000
26001 it('should be invalid if over max', function() {
26002 setInput('2015-01');
26003 expect(value.getText()).toContain('');
26004 expect(valid.getText()).toContain('myForm.input.$valid = false');
26005 });
26006 </file>
26007 </example>
26008 */
26009 'month': createDateInputType('month', MONTH_REGEXP,
26010 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
26011 'yyyy-MM'),
26012
26013 /**
26014 * @ngdoc input
26015 * @name input[number]
26016 *
26017 * @description
26018 * Text input with number validation and transformation. Sets the `number` validation
26019 * error if not a valid number.
26020 *
26021 * <div class="alert alert-warning">
26022 * The model must always be of type `number` otherwise AngularJS will throw an error.
26023 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
26024 * error docs for more information and an example of how to convert your model if necessary.
26025 * </div>
26026 *
26027 *
26028 *
26029 * @knownIssue
26030 *
26031 * ### HTML5 constraint validation and `allowInvalid`
26032 *
26033 * In browsers that follow the
26034 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
26035 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
26036 * If a non-number is entered in the input, the browser will report the value as an empty string,
26037 * which means the view / model values in `ngModel` and subsequently the scope value
26038 * will also be an empty string.
26039 *
26040 * @knownIssue
26041 *
26042 * ### Large numbers and `step` validation
26043 *
26044 * The `step` validation will not work correctly for very large numbers (e.g. 9999999999) due to
26045 * Javascript's arithmetic limitations. If you need to handle large numbers, purpose-built
26046 * libraries (e.g. https://github.com/MikeMcl/big.js/), can be included into AngularJS by
26047 * {@link guide/forms#modifying-built-in-validators overwriting the validators}
26048 * for `number` and / or `step`, or by {@link guide/forms#custom-validation applying custom validators}
26049 * to an `input[text]` element. The source for `input[number]` type can be used as a starting
26050 * point for both implementations.
26051 *
26052 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
26053 * @param {string=} name Property name of the form under which the control is published.
26054 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
26055 * Can be interpolated.
26056 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
26057 * Can be interpolated.
26058 * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`,
26059 * but does not trigger HTML5 native validation. Takes an expression.
26060 * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`,
26061 * but does not trigger HTML5 native validation. Takes an expression.
26062 * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint.
26063 * Can be interpolated.
26064 * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint,
26065 * but does not trigger HTML5 native validation. Takes an expression.
26066 * @param {string=} required Sets `required` validation error key if the value is not entered.
26067 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26068 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26069 * `required` when you want to data-bind to the `required` attribute.
26070 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
26071 * minlength.
26072 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
26073 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
26074 * any length.
26075 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
26076 * that contains the regular expression body that will be converted to a regular expression
26077 * as in the ngPattern directive.
26078 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
26079 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
26080 * If the expression evaluates to a RegExp object, then this is used directly.
26081 * If the expression evaluates to a string, then it will be converted to a RegExp
26082 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
26083 * `new RegExp('^abc$')`.<br />
26084 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
26085 * start at the index of the last search's match, thus not taking the whole input value into
26086 * account.
26087 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
26088 * interaction with the input element.
26089 *
26090 * @example
26091 <example name="number-input-directive" module="numberExample">
26092 <file name="index.html">
26093 <script>
26094 angular.module('numberExample', [])
26095 .controller('ExampleController', ['$scope', function($scope) {
26096 $scope.example = {
26097 value: 12
26098 };
26099 }]);
26100 </script>
26101 <form name="myForm" ng-controller="ExampleController">
26102 <label>Number:
26103 <input type="number" name="input" ng-model="example.value"
26104 min="0" max="99" required>
26105 </label>
26106 <div role="alert">
26107 <span class="error" ng-show="myForm.input.$error.required">
26108 Required!</span>
26109 <span class="error" ng-show="myForm.input.$error.number">
26110 Not valid number!</span>
26111 </div>
26112 <tt>value = {{example.value}}</tt><br/>
26113 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
26114 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
26115 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
26116 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
26117 </form>
26118 </file>
26119 <file name="protractor.js" type="protractor">
26120 var value = element(by.binding('example.value'));
26121 var valid = element(by.binding('myForm.input.$valid'));
26122 var input = element(by.model('example.value'));
26123
26124 it('should initialize to model', function() {
26125 expect(value.getText()).toContain('12');
26126 expect(valid.getText()).toContain('true');
26127 });
26128
26129 it('should be invalid if empty', function() {
26130 input.clear();
26131 input.sendKeys('');
26132 expect(value.getText()).toEqual('value =');
26133 expect(valid.getText()).toContain('false');
26134 });
26135
26136 it('should be invalid if over max', function() {
26137 input.clear();
26138 input.sendKeys('123');
26139 expect(value.getText()).toEqual('value =');
26140 expect(valid.getText()).toContain('false');
26141 });
26142 </file>
26143 </example>
26144 */
26145 'number': numberInputType,
26146
26147
26148 /**
26149 * @ngdoc input
26150 * @name input[url]
26151 *
26152 * @description
26153 * Text input with URL validation. Sets the `url` validation error key if the content is not a
26154 * valid URL.
26155 *
26156 * <div class="alert alert-warning">
26157 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
26158 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
26159 * the built-in validators (see the {@link guide/forms Forms guide})
26160 * </div>
26161 *
26162 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
26163 * @param {string=} name Property name of the form under which the control is published.
26164 * @param {string=} required Sets `required` validation error key if the value is not entered.
26165 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26166 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26167 * `required` when you want to data-bind to the `required` attribute.
26168 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
26169 * minlength.
26170 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
26171 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
26172 * any length.
26173 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
26174 * that contains the regular expression body that will be converted to a regular expression
26175 * as in the ngPattern directive.
26176 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
26177 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
26178 * If the expression evaluates to a RegExp object, then this is used directly.
26179 * If the expression evaluates to a string, then it will be converted to a RegExp
26180 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
26181 * `new RegExp('^abc$')`.<br />
26182 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
26183 * start at the index of the last search's match, thus not taking the whole input value into
26184 * account.
26185 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
26186 * interaction with the input element.
26187 *
26188 * @example
26189 <example name="url-input-directive" module="urlExample">
26190 <file name="index.html">
26191 <script>
26192 angular.module('urlExample', [])
26193 .controller('ExampleController', ['$scope', function($scope) {
26194 $scope.url = {
26195 text: 'http://google.com'
26196 };
26197 }]);
26198 </script>
26199 <form name="myForm" ng-controller="ExampleController">
26200 <label>URL:
26201 <input type="url" name="input" ng-model="url.text" required>
26202 <label>
26203 <div role="alert">
26204 <span class="error" ng-show="myForm.input.$error.required">
26205 Required!</span>
26206 <span class="error" ng-show="myForm.input.$error.url">
26207 Not valid url!</span>
26208 </div>
26209 <tt>text = {{url.text}}</tt><br/>
26210 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
26211 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
26212 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
26213 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
26214 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
26215 </form>
26216 </file>
26217 <file name="protractor.js" type="protractor">
26218 var text = element(by.binding('url.text'));
26219 var valid = element(by.binding('myForm.input.$valid'));
26220 var input = element(by.model('url.text'));
26221
26222 it('should initialize to model', function() {
26223 expect(text.getText()).toContain('http://google.com');
26224 expect(valid.getText()).toContain('true');
26225 });
26226
26227 it('should be invalid if empty', function() {
26228 input.clear();
26229 input.sendKeys('');
26230
26231 expect(text.getText()).toEqual('text =');
26232 expect(valid.getText()).toContain('false');
26233 });
26234
26235 it('should be invalid if not url', function() {
26236 input.clear();
26237 input.sendKeys('box');
26238
26239 expect(valid.getText()).toContain('false');
26240 });
26241 </file>
26242 </example>
26243 */
26244 'url': urlInputType,
26245
26246
26247 /**
26248 * @ngdoc input
26249 * @name input[email]
26250 *
26251 * @description
26252 * Text input with email validation. Sets the `email` validation error key if not a valid email
26253 * address.
26254 *
26255 * <div class="alert alert-warning">
26256 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
26257 * used in Chromium, which may not fulfill your app's requirements.
26258 * If you need stricter (e.g. requiring a top-level domain), or more relaxed validation
26259 * (e.g. allowing IPv6 address literals) you can use `ng-pattern` or
26260 * modify the built-in validators (see the {@link guide/forms Forms guide}).
26261 * </div>
26262 *
26263 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
26264 * @param {string=} name Property name of the form under which the control is published.
26265 * @param {string=} required Sets `required` validation error key if the value is not entered.
26266 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26267 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26268 * `required` when you want to data-bind to the `required` attribute.
26269 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
26270 * minlength.
26271 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
26272 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
26273 * any length.
26274 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
26275 * that contains the regular expression body that will be converted to a regular expression
26276 * as in the ngPattern directive.
26277 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
26278 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
26279 * If the expression evaluates to a RegExp object, then this is used directly.
26280 * If the expression evaluates to a string, then it will be converted to a RegExp
26281 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
26282 * `new RegExp('^abc$')`.<br />
26283 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
26284 * start at the index of the last search's match, thus not taking the whole input value into
26285 * account.
26286 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
26287 * interaction with the input element.
26288 *
26289 * @example
26290 <example name="email-input-directive" module="emailExample">
26291 <file name="index.html">
26292 <script>
26293 angular.module('emailExample', [])
26294 .controller('ExampleController', ['$scope', function($scope) {
26295 $scope.email = {
26296 text: 'me@example.com'
26297 };
26298 }]);
26299 </script>
26300 <form name="myForm" ng-controller="ExampleController">
26301 <label>Email:
26302 <input type="email" name="input" ng-model="email.text" required>
26303 </label>
26304 <div role="alert">
26305 <span class="error" ng-show="myForm.input.$error.required">
26306 Required!</span>
26307 <span class="error" ng-show="myForm.input.$error.email">
26308 Not valid email!</span>
26309 </div>
26310 <tt>text = {{email.text}}</tt><br/>
26311 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
26312 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
26313 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
26314 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
26315 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
26316 </form>
26317 </file>
26318 <file name="protractor.js" type="protractor">
26319 var text = element(by.binding('email.text'));
26320 var valid = element(by.binding('myForm.input.$valid'));
26321 var input = element(by.model('email.text'));
26322
26323 it('should initialize to model', function() {
26324 expect(text.getText()).toContain('me@example.com');
26325 expect(valid.getText()).toContain('true');
26326 });
26327
26328 it('should be invalid if empty', function() {
26329 input.clear();
26330 input.sendKeys('');
26331 expect(text.getText()).toEqual('text =');
26332 expect(valid.getText()).toContain('false');
26333 });
26334
26335 it('should be invalid if not email', function() {
26336 input.clear();
26337 input.sendKeys('xxx');
26338
26339 expect(valid.getText()).toContain('false');
26340 });
26341 </file>
26342 </example>
26343 */
26344 'email': emailInputType,
26345
26346
26347 /**
26348 * @ngdoc input
26349 * @name input[radio]
26350 *
26351 * @description
26352 * HTML radio button.
26353 *
26354 * **Note:**<br>
26355 * All inputs controlled by {@link ngModel ngModel} (including those of type `radio`) will use the
26356 * value of their `name` attribute to determine the property under which their
26357 * {@link ngModel.NgModelController NgModelController} will be published on the parent
26358 * {@link form.FormController FormController}. Thus, if you use the same `name` for multiple
26359 * inputs of a form (e.g. a group of radio inputs), only _one_ `NgModelController` will be
26360 * published on the parent `FormController` under that name. The rest of the controllers will
26361 * continue to work as expected, but you won't be able to access them as properties on the parent
26362 * `FormController`.
26363 *
26364 * <div class="alert alert-info">
26365 * <p>
26366 * In plain HTML forms, the `name` attribute is used to identify groups of radio inputs, so
26367 * that the browser can manage their state (checked/unchecked) based on the state of other
26368 * inputs in the same group.
26369 * </p>
26370 * <p>
26371 * In AngularJS forms, this is not necessary. The input's state will be updated based on the
26372 * value of the underlying model data.
26373 * </p>
26374 * </div>
26375 *
26376 * <div class="alert alert-success">
26377 * If you omit the `name` attribute on a radio input, `ngModel` will automatically assign it a
26378 * unique name.
26379 * </div>
26380 *
26381 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
26382 * @param {string} value The value to which the `ngModel` expression should be set when selected.
26383 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
26384 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
26385 * @param {string=} name Property name of the form under which the control is published.
26386 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
26387 * interaction with the input element.
26388 * @param {string} ngValue AngularJS expression to which `ngModel` will be be set when the radio
26389 * is selected. Should be used instead of the `value` attribute if you need
26390 * a non-string `ngModel` (`boolean`, `array`, ...).
26391 *
26392 * @example
26393 <example name="radio-input-directive" module="radioExample">
26394 <file name="index.html">
26395 <script>
26396 angular.module('radioExample', [])
26397 .controller('ExampleController', ['$scope', function($scope) {
26398 $scope.color = {
26399 name: 'blue'
26400 };
26401 $scope.specialValue = {
26402 "id": "12345",
26403 "value": "green"
26404 };
26405 }]);
26406 </script>
26407 <form name="myForm" ng-controller="ExampleController">
26408 <label>
26409 <input type="radio" ng-model="color.name" value="red">
26410 Red
26411 </label><br/>
26412 <label>
26413 <input type="radio" ng-model="color.name" ng-value="specialValue">
26414 Green
26415 </label><br/>
26416 <label>
26417 <input type="radio" ng-model="color.name" value="blue">
26418 Blue
26419 </label><br/>
26420 <tt>color = {{color.name | json}}</tt><br/>
26421 </form>
26422 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
26423 </file>
26424 <file name="protractor.js" type="protractor">
26425 it('should change state', function() {
26426 var inputs = element.all(by.model('color.name'));
26427 var color = element(by.binding('color.name'));
26428
26429 expect(color.getText()).toContain('blue');
26430
26431 inputs.get(0).click();
26432 expect(color.getText()).toContain('red');
26433
26434 inputs.get(1).click();
26435 expect(color.getText()).toContain('green');
26436 });
26437 </file>
26438 </example>
26439 */
26440 'radio': radioInputType,
26441
26442 /**
26443 * @ngdoc input
26444 * @name input[range]
26445 *
26446 * @description
26447 * Native range input with validation and transformation.
26448 *
26449 * The model for the range input must always be a `Number`.
26450 *
26451 * IE9 and other browsers that do not support the `range` type fall back
26452 * to a text input without any default values for `min`, `max` and `step`. Model binding,
26453 * validation and number parsing are nevertheless supported.
26454 *
26455 * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
26456 * in a way that never allows the input to hold an invalid value. That means:
26457 * - any non-numerical value is set to `(max + min) / 2`.
26458 * - any numerical value that is less than the current min val, or greater than the current max val
26459 * is set to the min / max val respectively.
26460 * - additionally, the current `step` is respected, so the nearest value that satisfies a step
26461 * is used.
26462 *
26463 * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range))
26464 * for more info.
26465 *
26466 * This has the following consequences for AngularJS:
26467 *
26468 * Since the element value should always reflect the current model value, a range input
26469 * will set the bound ngModel expression to the value that the browser has set for the
26470 * input element. For example, in the following input `<input type="range" ng-model="model.value">`,
26471 * if the application sets `model.value = null`, the browser will set the input to `'50'`.
26472 * AngularJS will then set the model to `50`, to prevent input and model value being out of sync.
26473 *
26474 * That means the model for range will immediately be set to `50` after `ngModel` has been
26475 * initialized. It also means a range input can never have the required error.
26476 *
26477 * This does not only affect changes to the model value, but also to the values of the `min`,
26478 * `max`, and `step` attributes. When these change in a way that will cause the browser to modify
26479 * the input value, AngularJS will also update the model value.
26480 *
26481 * Automatic value adjustment also means that a range input element can never have the `required`,
26482 * `min`, or `max` errors.
26483 *
26484 * However, `step` is currently only fully implemented by Firefox. Other browsers have problems
26485 * when the step value changes dynamically - they do not adjust the element value correctly, but
26486 * instead may set the `stepMismatch` error. If that's the case, the AngularJS will set the `step`
26487 * error on the input, and set the model to `undefined`.
26488 *
26489 * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do
26490 * not set the `min` and `max` attributes, which means that the browser won't automatically adjust
26491 * the input value based on their values, and will always assume min = 0, max = 100, and step = 1.
26492 *
26493 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
26494 * @param {string=} name Property name of the form under which the control is published.
26495 * @param {string=} min Sets the `min` validation to ensure that the value entered is greater
26496 * than `min`. Can be interpolated.
26497 * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`.
26498 * Can be interpolated.
26499 * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step`
26500 * Can be interpolated.
26501 * @param {expression=} ngChange AngularJS expression to be executed when the ngModel value changes due
26502 * to user interaction with the input element.
26503 * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the
26504 * element. **Note** : `ngChecked` should not be used alongside `ngModel`.
26505 * Checkout {@link ng.directive:ngChecked ngChecked} for usage.
26506 *
26507 * @example
26508 <example name="range-input-directive" module="rangeExample">
26509 <file name="index.html">
26510 <script>
26511 angular.module('rangeExample', [])
26512 .controller('ExampleController', ['$scope', function($scope) {
26513 $scope.value = 75;
26514 $scope.min = 10;
26515 $scope.max = 90;
26516 }]);
26517 </script>
26518 <form name="myForm" ng-controller="ExampleController">
26519
26520 Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}">
26521 <hr>
26522 Model as number: <input type="number" ng-model="value"><br>
26523 Min: <input type="number" ng-model="min"><br>
26524 Max: <input type="number" ng-model="max"><br>
26525 value = <code>{{value}}</code><br/>
26526 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
26527 myForm.range.$error = <code>{{myForm.range.$error}}</code>
26528 </form>
26529 </file>
26530 </example>
26531
26532 * ## Range Input with ngMin & ngMax attributes
26533
26534 * @example
26535 <example name="range-input-directive-ng" module="rangeExample">
26536 <file name="index.html">
26537 <script>
26538 angular.module('rangeExample', [])
26539 .controller('ExampleController', ['$scope', function($scope) {
26540 $scope.value = 75;
26541 $scope.min = 10;
26542 $scope.max = 90;
26543 }]);
26544 </script>
26545 <form name="myForm" ng-controller="ExampleController">
26546 Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max">
26547 <hr>
26548 Model as number: <input type="number" ng-model="value"><br>
26549 Min: <input type="number" ng-model="min"><br>
26550 Max: <input type="number" ng-model="max"><br>
26551 value = <code>{{value}}</code><br/>
26552 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
26553 myForm.range.$error = <code>{{myForm.range.$error}}</code>
26554 </form>
26555 </file>
26556 </example>
26557
26558 */
26559 'range': rangeInputType,
26560
26561 /**
26562 * @ngdoc input
26563 * @name input[checkbox]
26564 *
26565 * @description
26566 * HTML checkbox.
26567 *
26568 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
26569 * @param {string=} name Property name of the form under which the control is published.
26570 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
26571 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
26572 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
26573 * interaction with the input element.
26574 *
26575 * @example
26576 <example name="checkbox-input-directive" module="checkboxExample">
26577 <file name="index.html">
26578 <script>
26579 angular.module('checkboxExample', [])
26580 .controller('ExampleController', ['$scope', function($scope) {
26581 $scope.checkboxModel = {
26582 value1 : true,
26583 value2 : 'YES'
26584 };
26585 }]);
26586 </script>
26587 <form name="myForm" ng-controller="ExampleController">
26588 <label>Value1:
26589 <input type="checkbox" ng-model="checkboxModel.value1">
26590 </label><br/>
26591 <label>Value2:
26592 <input type="checkbox" ng-model="checkboxModel.value2"
26593 ng-true-value="'YES'" ng-false-value="'NO'">
26594 </label><br/>
26595 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
26596 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
26597 </form>
26598 </file>
26599 <file name="protractor.js" type="protractor">
26600 it('should change state', function() {
26601 var value1 = element(by.binding('checkboxModel.value1'));
26602 var value2 = element(by.binding('checkboxModel.value2'));
26603
26604 expect(value1.getText()).toContain('true');
26605 expect(value2.getText()).toContain('YES');
26606
26607 element(by.model('checkboxModel.value1')).click();
26608 element(by.model('checkboxModel.value2')).click();
26609
26610 expect(value1.getText()).toContain('false');
26611 expect(value2.getText()).toContain('NO');
26612 });
26613 </file>
26614 </example>
26615 */
26616 'checkbox': checkboxInputType,
26617
26618 'hidden': noop,
26619 'button': noop,
26620 'submit': noop,
26621 'reset': noop,
26622 'file': noop
26623};
26624
26625function stringBasedInputType(ctrl) {
26626 ctrl.$formatters.push(function(value) {
26627 return ctrl.$isEmpty(value) ? value : value.toString();
26628 });
26629}
26630
26631function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
26632 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
26633 stringBasedInputType(ctrl);
26634}
26635
26636function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
26637 var type = lowercase(element[0].type);
26638
26639 // In composition mode, users are still inputting intermediate text buffer,
26640 // hold the listener until composition is done.
26641 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
26642 if (!$sniffer.android) {
26643 var composing = false;
26644
26645 element.on('compositionstart', function() {
26646 composing = true;
26647 });
26648
26649 // Support: IE9+
26650 element.on('compositionupdate', function(ev) {
26651 // End composition when ev.data is empty string on 'compositionupdate' event.
26652 // When the input de-focusses (e.g. by clicking away), IE triggers 'compositionupdate'
26653 // instead of 'compositionend'.
26654 if (isUndefined(ev.data) || ev.data === '') {
26655 composing = false;
26656 }
26657 });
26658
26659 element.on('compositionend', function() {
26660 composing = false;
26661 listener();
26662 });
26663 }
26664
26665 var timeout;
26666
26667 var listener = function(ev) {
26668 if (timeout) {
26669 $browser.defer.cancel(timeout);
26670 timeout = null;
26671 }
26672 if (composing) return;
26673 var value = element.val(),
26674 event = ev && ev.type;
26675
26676 // By default we will trim the value
26677 // If the attribute ng-trim exists we will avoid trimming
26678 // If input type is 'password', the value is never trimmed
26679 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
26680 value = trim(value);
26681 }
26682
26683 // If a control is suffering from bad input (due to native validators), browsers discard its
26684 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
26685 // control's value is the same empty value twice in a row.
26686 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
26687 ctrl.$setViewValue(value, event);
26688 }
26689 };
26690
26691 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
26692 // input event on backspace, delete or cut
26693 if ($sniffer.hasEvent('input')) {
26694 element.on('input', listener);
26695 } else {
26696 var deferListener = function(ev, input, origValue) {
26697 if (!timeout) {
26698 timeout = $browser.defer(function() {
26699 timeout = null;
26700 if (!input || input.value !== origValue) {
26701 listener(ev);
26702 }
26703 });
26704 }
26705 };
26706
26707 element.on('keydown', /** @this */ function(event) {
26708 var key = event.keyCode;
26709
26710 // ignore
26711 // command modifiers arrows
26712 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
26713
26714 deferListener(event, this, this.value);
26715 });
26716
26717 // if user modifies input value using context menu in IE, we need "paste", "cut" and "drop" events to catch it
26718 if ($sniffer.hasEvent('paste')) {
26719 element.on('paste cut drop', deferListener);
26720 }
26721 }
26722
26723 // if user paste into input using mouse on older browser
26724 // or form autocomplete on newer browser, we need "change" event to catch it
26725 element.on('change', listener);
26726
26727 // Some native input types (date-family) have the ability to change validity without
26728 // firing any input/change events.
26729 // For these event types, when native validators are present and the browser supports the type,
26730 // check for validity changes on various DOM events.
26731 if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) {
26732 element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) {
26733 if (!timeout) {
26734 var validity = this[VALIDITY_STATE_PROPERTY];
26735 var origBadInput = validity.badInput;
26736 var origTypeMismatch = validity.typeMismatch;
26737 timeout = $browser.defer(function() {
26738 timeout = null;
26739 if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) {
26740 listener(ev);
26741 }
26742 });
26743 }
26744 });
26745 }
26746
26747 ctrl.$render = function() {
26748 // Workaround for Firefox validation #12102.
26749 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
26750 if (element.val() !== value) {
26751 element.val(value);
26752 }
26753 };
26754}
26755
26756function weekParser(isoWeek, existingDate) {
26757 if (isDate(isoWeek)) {
26758 return isoWeek;
26759 }
26760
26761 if (isString(isoWeek)) {
26762 WEEK_REGEXP.lastIndex = 0;
26763 var parts = WEEK_REGEXP.exec(isoWeek);
26764 if (parts) {
26765 var year = +parts[1],
26766 week = +parts[2],
26767 hours = 0,
26768 minutes = 0,
26769 seconds = 0,
26770 milliseconds = 0,
26771 firstThurs = getFirstThursdayOfYear(year),
26772 addDays = (week - 1) * 7;
26773
26774 if (existingDate) {
26775 hours = existingDate.getHours();
26776 minutes = existingDate.getMinutes();
26777 seconds = existingDate.getSeconds();
26778 milliseconds = existingDate.getMilliseconds();
26779 }
26780
26781 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
26782 }
26783 }
26784
26785 return NaN;
26786}
26787
26788function createDateParser(regexp, mapping) {
26789 return function(iso, previousDate) {
26790 var parts, map;
26791
26792 if (isDate(iso)) {
26793 return iso;
26794 }
26795
26796 if (isString(iso)) {
26797 // When a date is JSON'ified to wraps itself inside of an extra
26798 // set of double quotes. This makes the date parsing code unable
26799 // to match the date string and parse it as a date.
26800 if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') {
26801 iso = iso.substring(1, iso.length - 1);
26802 }
26803 if (ISO_DATE_REGEXP.test(iso)) {
26804 return new Date(iso);
26805 }
26806 regexp.lastIndex = 0;
26807 parts = regexp.exec(iso);
26808
26809 if (parts) {
26810 parts.shift();
26811 if (previousDate) {
26812 map = {
26813 yyyy: previousDate.getFullYear(),
26814 MM: previousDate.getMonth() + 1,
26815 dd: previousDate.getDate(),
26816 HH: previousDate.getHours(),
26817 mm: previousDate.getMinutes(),
26818 ss: previousDate.getSeconds(),
26819 sss: previousDate.getMilliseconds() / 1000
26820 };
26821 } else {
26822 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
26823 }
26824
26825 forEach(parts, function(part, index) {
26826 if (index < mapping.length) {
26827 map[mapping[index]] = +part;
26828 }
26829 });
26830
26831 var date = new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
26832 if (map.yyyy < 100) {
26833 // In the constructor, 2-digit years map to 1900-1999.
26834 // Use `setFullYear()` to set the correct year.
26835 date.setFullYear(map.yyyy);
26836 }
26837
26838 return date;
26839 }
26840 }
26841
26842 return NaN;
26843 };
26844}
26845
26846function createDateInputType(type, regexp, parseDate, format) {
26847 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
26848 badInputChecker(scope, element, attr, ctrl, type);
26849 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
26850
26851 var isTimeType = type === 'time' || type === 'datetimelocal';
26852 var previousDate;
26853 var previousTimezone;
26854
26855 ctrl.$parsers.push(function(value) {
26856 if (ctrl.$isEmpty(value)) return null;
26857
26858 if (regexp.test(value)) {
26859 // Note: We cannot read ctrl.$modelValue, as there might be a different
26860 // parser/formatter in the processing chain so that the model
26861 // contains some different data format!
26862 return parseDateAndConvertTimeZoneToLocal(value, previousDate);
26863 }
26864 ctrl.$$parserName = type;
26865 return undefined;
26866 });
26867
26868 ctrl.$formatters.push(function(value) {
26869 if (value && !isDate(value)) {
26870 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
26871 }
26872 if (isValidDate(value)) {
26873 previousDate = value;
26874 var timezone = ctrl.$options.getOption('timezone');
26875
26876 if (timezone) {
26877 previousTimezone = timezone;
26878 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
26879 }
26880
26881 return formatter(value, timezone);
26882 } else {
26883 previousDate = null;
26884 previousTimezone = null;
26885 return '';
26886 }
26887 });
26888
26889 if (isDefined(attr.min) || attr.ngMin) {
26890 var minVal = attr.min || $parse(attr.ngMin)(scope);
26891 var parsedMinVal = parseObservedDateValue(minVal);
26892
26893 ctrl.$validators.min = function(value) {
26894 return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal;
26895 };
26896 attr.$observe('min', function(val) {
26897 if (val !== minVal) {
26898 parsedMinVal = parseObservedDateValue(val);
26899 minVal = val;
26900 ctrl.$validate();
26901 }
26902 });
26903 }
26904
26905 if (isDefined(attr.max) || attr.ngMax) {
26906 var maxVal = attr.max || $parse(attr.ngMax)(scope);
26907 var parsedMaxVal = parseObservedDateValue(maxVal);
26908
26909 ctrl.$validators.max = function(value) {
26910 return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal;
26911 };
26912 attr.$observe('max', function(val) {
26913 if (val !== maxVal) {
26914 parsedMaxVal = parseObservedDateValue(val);
26915 maxVal = val;
26916 ctrl.$validate();
26917 }
26918 });
26919 }
26920
26921 function isValidDate(value) {
26922 // Invalid Date: getTime() returns NaN
26923 return value && !(value.getTime && value.getTime() !== value.getTime());
26924 }
26925
26926 function parseObservedDateValue(val) {
26927 return isDefined(val) && !isDate(val) ? parseDateAndConvertTimeZoneToLocal(val) || undefined : val;
26928 }
26929
26930 function parseDateAndConvertTimeZoneToLocal(value, previousDate) {
26931 var timezone = ctrl.$options.getOption('timezone');
26932
26933 if (previousTimezone && previousTimezone !== timezone) {
26934 // If the timezone has changed, adjust the previousDate to the default timezone
26935 // so that the new date is converted with the correct timezone offset
26936 previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone));
26937 }
26938
26939 var parsedDate = parseDate(value, previousDate);
26940
26941 if (!isNaN(parsedDate) && timezone) {
26942 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
26943 }
26944 return parsedDate;
26945 }
26946
26947 function formatter(value, timezone) {
26948 var targetFormat = format;
26949
26950 if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) {
26951 targetFormat = format
26952 .replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat'))
26953 .replace(/:$/, '');
26954 }
26955
26956 var formatted = $filter('date')(value, targetFormat, timezone);
26957
26958 if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) {
26959 formatted = formatted.replace(/(?::00)?(?:\.000)?$/, '');
26960 }
26961
26962 return formatted;
26963 }
26964 };
26965}
26966
26967function badInputChecker(scope, element, attr, ctrl, parserName) {
26968 var node = element[0];
26969 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
26970 if (nativeValidation) {
26971 ctrl.$parsers.push(function(value) {
26972 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
26973 if (validity.badInput || validity.typeMismatch) {
26974 ctrl.$$parserName = parserName;
26975 return undefined;
26976 }
26977
26978 return value;
26979 });
26980 }
26981}
26982
26983function numberFormatterParser(ctrl) {
26984 ctrl.$parsers.push(function(value) {
26985 if (ctrl.$isEmpty(value)) return null;
26986 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
26987
26988 ctrl.$$parserName = 'number';
26989 return undefined;
26990 });
26991
26992 ctrl.$formatters.push(function(value) {
26993 if (!ctrl.$isEmpty(value)) {
26994 if (!isNumber(value)) {
26995 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
26996 }
26997 value = value.toString();
26998 }
26999 return value;
27000 });
27001}
27002
27003function parseNumberAttrVal(val) {
27004 if (isDefined(val) && !isNumber(val)) {
27005 val = parseFloat(val);
27006 }
27007 return !isNumberNaN(val) ? val : undefined;
27008}
27009
27010function isNumberInteger(num) {
27011 // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066
27012 // (minus the assumption that `num` is a number)
27013
27014 // eslint-disable-next-line no-bitwise
27015 return (num | 0) === num;
27016}
27017
27018function countDecimals(num) {
27019 var numString = num.toString();
27020 var decimalSymbolIndex = numString.indexOf('.');
27021
27022 if (decimalSymbolIndex === -1) {
27023 if (-1 < num && num < 1) {
27024 // It may be in the exponential notation format (`1e-X`)
27025 var match = /e-(\d+)$/.exec(numString);
27026
27027 if (match) {
27028 return Number(match[1]);
27029 }
27030 }
27031
27032 return 0;
27033 }
27034
27035 return numString.length - decimalSymbolIndex - 1;
27036}
27037
27038function isValidForStep(viewValue, stepBase, step) {
27039 // At this point `stepBase` and `step` are expected to be non-NaN values
27040 // and `viewValue` is expected to be a valid stringified number.
27041 var value = Number(viewValue);
27042
27043 var isNonIntegerValue = !isNumberInteger(value);
27044 var isNonIntegerStepBase = !isNumberInteger(stepBase);
27045 var isNonIntegerStep = !isNumberInteger(step);
27046
27047 // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or
27048 // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers.
27049 if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) {
27050 var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0;
27051 var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0;
27052 var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0;
27053
27054 var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals);
27055 var multiplier = Math.pow(10, decimalCount);
27056
27057 value = value * multiplier;
27058 stepBase = stepBase * multiplier;
27059 step = step * multiplier;
27060
27061 if (isNonIntegerValue) value = Math.round(value);
27062 if (isNonIntegerStepBase) stepBase = Math.round(stepBase);
27063 if (isNonIntegerStep) step = Math.round(step);
27064 }
27065
27066 return (value - stepBase) % step === 0;
27067}
27068
27069function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
27070 badInputChecker(scope, element, attr, ctrl, 'number');
27071 numberFormatterParser(ctrl);
27072 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
27073
27074 var parsedMinVal;
27075
27076 if (isDefined(attr.min) || attr.ngMin) {
27077 var minVal = attr.min || $parse(attr.ngMin)(scope);
27078 parsedMinVal = parseNumberAttrVal(minVal);
27079
27080 ctrl.$validators.min = function(modelValue, viewValue) {
27081 return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal;
27082 };
27083
27084 attr.$observe('min', function(val) {
27085 if (val !== minVal) {
27086 parsedMinVal = parseNumberAttrVal(val);
27087 minVal = val;
27088 // TODO(matsko): implement validateLater to reduce number of validations
27089 ctrl.$validate();
27090 }
27091 });
27092 }
27093
27094 if (isDefined(attr.max) || attr.ngMax) {
27095 var maxVal = attr.max || $parse(attr.ngMax)(scope);
27096 var parsedMaxVal = parseNumberAttrVal(maxVal);
27097
27098 ctrl.$validators.max = function(modelValue, viewValue) {
27099 return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal;
27100 };
27101
27102 attr.$observe('max', function(val) {
27103 if (val !== maxVal) {
27104 parsedMaxVal = parseNumberAttrVal(val);
27105 maxVal = val;
27106 // TODO(matsko): implement validateLater to reduce number of validations
27107 ctrl.$validate();
27108 }
27109 });
27110 }
27111
27112 if (isDefined(attr.step) || attr.ngStep) {
27113 var stepVal = attr.step || $parse(attr.ngStep)(scope);
27114 var parsedStepVal = parseNumberAttrVal(stepVal);
27115
27116 ctrl.$validators.step = function(modelValue, viewValue) {
27117 return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) ||
27118 isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal);
27119 };
27120
27121 attr.$observe('step', function(val) {
27122 // TODO(matsko): implement validateLater to reduce number of validations
27123 if (val !== stepVal) {
27124 parsedStepVal = parseNumberAttrVal(val);
27125 stepVal = val;
27126 ctrl.$validate();
27127 }
27128
27129 });
27130
27131 }
27132}
27133
27134function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
27135 badInputChecker(scope, element, attr, ctrl, 'range');
27136 numberFormatterParser(ctrl);
27137 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
27138
27139 var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
27140 minVal = supportsRange ? 0 : undefined,
27141 maxVal = supportsRange ? 100 : undefined,
27142 stepVal = supportsRange ? 1 : undefined,
27143 validity = element[0].validity,
27144 hasMinAttr = isDefined(attr.min),
27145 hasMaxAttr = isDefined(attr.max),
27146 hasStepAttr = isDefined(attr.step);
27147
27148 var originalRender = ctrl.$render;
27149
27150 ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ?
27151 //Browsers that implement range will set these values automatically, but reading the adjusted values after
27152 //$render would cause the min / max validators to be applied with the wrong value
27153 function rangeRender() {
27154 originalRender();
27155 ctrl.$setViewValue(element.val());
27156 } :
27157 originalRender;
27158
27159 if (hasMinAttr) {
27160 minVal = parseNumberAttrVal(attr.min);
27161
27162 ctrl.$validators.min = supportsRange ?
27163 // Since all browsers set the input to a valid value, we don't need to check validity
27164 function noopMinValidator() { return true; } :
27165 // non-support browsers validate the min val
27166 function minValidator(modelValue, viewValue) {
27167 return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
27168 };
27169
27170 setInitialValueAndObserver('min', minChange);
27171 }
27172
27173 if (hasMaxAttr) {
27174 maxVal = parseNumberAttrVal(attr.max);
27175
27176 ctrl.$validators.max = supportsRange ?
27177 // Since all browsers set the input to a valid value, we don't need to check validity
27178 function noopMaxValidator() { return true; } :
27179 // non-support browsers validate the max val
27180 function maxValidator(modelValue, viewValue) {
27181 return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
27182 };
27183
27184 setInitialValueAndObserver('max', maxChange);
27185 }
27186
27187 if (hasStepAttr) {
27188 stepVal = parseNumberAttrVal(attr.step);
27189
27190 ctrl.$validators.step = supportsRange ?
27191 function nativeStepValidator() {
27192 // Currently, only FF implements the spec on step change correctly (i.e. adjusting the
27193 // input element value to a valid value). It's possible that other browsers set the stepMismatch
27194 // validity error instead, so we can at least report an error in that case.
27195 return !validity.stepMismatch;
27196 } :
27197 // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would
27198 function stepValidator(modelValue, viewValue) {
27199 return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
27200 isValidForStep(viewValue, minVal || 0, stepVal);
27201 };
27202
27203 setInitialValueAndObserver('step', stepChange);
27204 }
27205
27206 function setInitialValueAndObserver(htmlAttrName, changeFn) {
27207 // interpolated attributes set the attribute value only after a digest, but we need the
27208 // attribute value when the input is first rendered, so that the browser can adjust the
27209 // input value based on the min/max value
27210 element.attr(htmlAttrName, attr[htmlAttrName]);
27211 var oldVal = attr[htmlAttrName];
27212 attr.$observe(htmlAttrName, function wrappedObserver(val) {
27213 if (val !== oldVal) {
27214 oldVal = val;
27215 changeFn(val);
27216 }
27217 });
27218 }
27219
27220 function minChange(val) {
27221 minVal = parseNumberAttrVal(val);
27222 // ignore changes before model is initialized
27223 if (isNumberNaN(ctrl.$modelValue)) {
27224 return;
27225 }
27226
27227 if (supportsRange) {
27228 var elVal = element.val();
27229 // IE11 doesn't set the el val correctly if the minVal is greater than the element value
27230 if (minVal > elVal) {
27231 elVal = minVal;
27232 element.val(elVal);
27233 }
27234 ctrl.$setViewValue(elVal);
27235 } else {
27236 // TODO(matsko): implement validateLater to reduce number of validations
27237 ctrl.$validate();
27238 }
27239 }
27240
27241 function maxChange(val) {
27242 maxVal = parseNumberAttrVal(val);
27243 // ignore changes before model is initialized
27244 if (isNumberNaN(ctrl.$modelValue)) {
27245 return;
27246 }
27247
27248 if (supportsRange) {
27249 var elVal = element.val();
27250 // IE11 doesn't set the el val correctly if the maxVal is less than the element value
27251 if (maxVal < elVal) {
27252 element.val(maxVal);
27253 // IE11 and Chrome don't set the value to the minVal when max < min
27254 elVal = maxVal < minVal ? minVal : maxVal;
27255 }
27256 ctrl.$setViewValue(elVal);
27257 } else {
27258 // TODO(matsko): implement validateLater to reduce number of validations
27259 ctrl.$validate();
27260 }
27261 }
27262
27263 function stepChange(val) {
27264 stepVal = parseNumberAttrVal(val);
27265 // ignore changes before model is initialized
27266 if (isNumberNaN(ctrl.$modelValue)) {
27267 return;
27268 }
27269
27270 // Some browsers don't adjust the input value correctly, but set the stepMismatch error
27271 if (!supportsRange) {
27272 // TODO(matsko): implement validateLater to reduce number of validations
27273 ctrl.$validate();
27274 } else if (ctrl.$viewValue !== element.val()) {
27275 ctrl.$setViewValue(element.val());
27276 }
27277 }
27278}
27279
27280function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
27281 // Note: no badInputChecker here by purpose as `url` is only a validation
27282 // in browsers, i.e. we can always read out input.value even if it is not valid!
27283 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
27284 stringBasedInputType(ctrl);
27285
27286 ctrl.$validators.url = function(modelValue, viewValue) {
27287 var value = modelValue || viewValue;
27288 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
27289 };
27290}
27291
27292function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
27293 // Note: no badInputChecker here by purpose as `url` is only a validation
27294 // in browsers, i.e. we can always read out input.value even if it is not valid!
27295 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
27296 stringBasedInputType(ctrl);
27297
27298 ctrl.$validators.email = function(modelValue, viewValue) {
27299 var value = modelValue || viewValue;
27300 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
27301 };
27302}
27303
27304function radioInputType(scope, element, attr, ctrl) {
27305 var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false';
27306 // make the name unique, if not defined
27307 if (isUndefined(attr.name)) {
27308 element.attr('name', nextUid());
27309 }
27310
27311 var listener = function(ev) {
27312 var value;
27313 if (element[0].checked) {
27314 value = attr.value;
27315 if (doTrim) {
27316 value = trim(value);
27317 }
27318 ctrl.$setViewValue(value, ev && ev.type);
27319 }
27320 };
27321
27322 element.on('change', listener);
27323
27324 ctrl.$render = function() {
27325 var value = attr.value;
27326 if (doTrim) {
27327 value = trim(value);
27328 }
27329 element[0].checked = (value === ctrl.$viewValue);
27330 };
27331
27332 attr.$observe('value', ctrl.$render);
27333}
27334
27335function parseConstantExpr($parse, context, name, expression, fallback) {
27336 var parseFn;
27337 if (isDefined(expression)) {
27338 parseFn = $parse(expression);
27339 if (!parseFn.constant) {
27340 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
27341 '`{1}`.', name, expression);
27342 }
27343 return parseFn(context);
27344 }
27345 return fallback;
27346}
27347
27348function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
27349 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
27350 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
27351
27352 var listener = function(ev) {
27353 ctrl.$setViewValue(element[0].checked, ev && ev.type);
27354 };
27355
27356 element.on('change', listener);
27357
27358 ctrl.$render = function() {
27359 element[0].checked = ctrl.$viewValue;
27360 };
27361
27362 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
27363 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
27364 // it to a boolean.
27365 ctrl.$isEmpty = function(value) {
27366 return value === false;
27367 };
27368
27369 ctrl.$formatters.push(function(value) {
27370 return equals(value, trueValue);
27371 });
27372
27373 ctrl.$parsers.push(function(value) {
27374 return value ? trueValue : falseValue;
27375 });
27376}
27377
27378
27379/**
27380 * @ngdoc directive
27381 * @name textarea
27382 * @restrict E
27383 *
27384 * @description
27385 * HTML textarea element control with AngularJS data-binding. The data-binding and validation
27386 * properties of this element are exactly the same as those of the
27387 * {@link ng.directive:input input element}.
27388 *
27389 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
27390 * @param {string=} name Property name of the form under which the control is published.
27391 * @param {string=} required Sets `required` validation error key if the value is not entered.
27392 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
27393 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
27394 * `required` when you want to data-bind to the `required` attribute.
27395 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
27396 * minlength.
27397 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
27398 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
27399 * length.
27400 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
27401 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
27402 * If the expression evaluates to a RegExp object, then this is used directly.
27403 * If the expression evaluates to a string, then it will be converted to a RegExp
27404 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
27405 * `new RegExp('^abc$')`.<br />
27406 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
27407 * start at the index of the last search's match, thus not taking the whole input value into
27408 * account.
27409 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
27410 * interaction with the input element.
27411 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
27412 *
27413 * @knownIssue
27414 *
27415 * When specifying the `placeholder` attribute of `<textarea>`, Internet Explorer will temporarily
27416 * insert the placeholder value as the textarea's content. If the placeholder value contains
27417 * interpolation (`{{ ... }}`), an error will be logged in the console when AngularJS tries to update
27418 * the value of the by-then-removed text node. This doesn't affect the functionality of the
27419 * textarea, but can be undesirable.
27420 *
27421 * You can work around this Internet Explorer issue by using `ng-attr-placeholder` instead of
27422 * `placeholder` on textareas, whenever you need interpolation in the placeholder value. You can
27423 * find more details on `ngAttr` in the
27424 * [Interpolation](guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) section of the
27425 * Developer Guide.
27426 */
27427
27428
27429/**
27430 * @ngdoc directive
27431 * @name input
27432 * @restrict E
27433 *
27434 * @description
27435 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
27436 * input state control, and validation.
27437 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
27438 *
27439 * <div class="alert alert-warning">
27440 * **Note:** Not every feature offered is available for all input types.
27441 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
27442 * </div>
27443 *
27444 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
27445 * @param {string=} name Property name of the form under which the control is published.
27446 * @param {string=} required Sets `required` validation error key if the value is not entered.
27447 * @param {boolean=} ngRequired Sets `required` attribute if set to true
27448 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
27449 * minlength.
27450 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
27451 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
27452 * length.
27453 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
27454 * value does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
27455 * If the expression evaluates to a RegExp object, then this is used directly.
27456 * If the expression evaluates to a string, then it will be converted to a RegExp
27457 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
27458 * `new RegExp('^abc$')`.<br />
27459 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
27460 * start at the index of the last search's match, thus not taking the whole input value into
27461 * account.
27462 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
27463 * interaction with the input element.
27464 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
27465 * This parameter is ignored for input[type=password] controls, which will never trim the
27466 * input.
27467 *
27468 * @example
27469 <example name="input-directive" module="inputExample">
27470 <file name="index.html">
27471 <script>
27472 angular.module('inputExample', [])
27473 .controller('ExampleController', ['$scope', function($scope) {
27474 $scope.user = {name: 'guest', last: 'visitor'};
27475 }]);
27476 </script>
27477 <div ng-controller="ExampleController">
27478 <form name="myForm">
27479 <label>
27480 User name:
27481 <input type="text" name="userName" ng-model="user.name" required>
27482 </label>
27483 <div role="alert">
27484 <span class="error" ng-show="myForm.userName.$error.required">
27485 Required!</span>
27486 </div>
27487 <label>
27488 Last name:
27489 <input type="text" name="lastName" ng-model="user.last"
27490 ng-minlength="3" ng-maxlength="10">
27491 </label>
27492 <div role="alert">
27493 <span class="error" ng-show="myForm.lastName.$error.minlength">
27494 Too short!</span>
27495 <span class="error" ng-show="myForm.lastName.$error.maxlength">
27496 Too long!</span>
27497 </div>
27498 </form>
27499 <hr>
27500 <tt>user = {{user}}</tt><br/>
27501 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
27502 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
27503 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
27504 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
27505 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
27506 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
27507 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
27508 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
27509 </div>
27510 </file>
27511 <file name="protractor.js" type="protractor">
27512 var user = element(by.exactBinding('user'));
27513 var userNameValid = element(by.binding('myForm.userName.$valid'));
27514 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
27515 var lastNameError = element(by.binding('myForm.lastName.$error'));
27516 var formValid = element(by.binding('myForm.$valid'));
27517 var userNameInput = element(by.model('user.name'));
27518 var userLastInput = element(by.model('user.last'));
27519
27520 it('should initialize to model', function() {
27521 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
27522 expect(userNameValid.getText()).toContain('true');
27523 expect(formValid.getText()).toContain('true');
27524 });
27525
27526 it('should be invalid if empty when required', function() {
27527 userNameInput.clear();
27528 userNameInput.sendKeys('');
27529
27530 expect(user.getText()).toContain('{"last":"visitor"}');
27531 expect(userNameValid.getText()).toContain('false');
27532 expect(formValid.getText()).toContain('false');
27533 });
27534
27535 it('should be valid if empty when min length is set', function() {
27536 userLastInput.clear();
27537 userLastInput.sendKeys('');
27538
27539 expect(user.getText()).toContain('{"name":"guest","last":""}');
27540 expect(lastNameValid.getText()).toContain('true');
27541 expect(formValid.getText()).toContain('true');
27542 });
27543
27544 it('should be invalid if less than required min length', function() {
27545 userLastInput.clear();
27546 userLastInput.sendKeys('xx');
27547
27548 expect(user.getText()).toContain('{"name":"guest"}');
27549 expect(lastNameValid.getText()).toContain('false');
27550 expect(lastNameError.getText()).toContain('minlength');
27551 expect(formValid.getText()).toContain('false');
27552 });
27553
27554 it('should be invalid if longer than max length', function() {
27555 userLastInput.clear();
27556 userLastInput.sendKeys('some ridiculously long name');
27557
27558 expect(user.getText()).toContain('{"name":"guest"}');
27559 expect(lastNameValid.getText()).toContain('false');
27560 expect(lastNameError.getText()).toContain('maxlength');
27561 expect(formValid.getText()).toContain('false');
27562 });
27563 </file>
27564 </example>
27565 */
27566var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
27567 function($browser, $sniffer, $filter, $parse) {
27568 return {
27569 restrict: 'E',
27570 require: ['?ngModel'],
27571 link: {
27572 pre: function(scope, element, attr, ctrls) {
27573 if (ctrls[0]) {
27574 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
27575 $browser, $filter, $parse);
27576 }
27577 }
27578 }
27579 };
27580}];
27581
27582
27583var hiddenInputBrowserCacheDirective = function() {
27584 var valueProperty = {
27585 configurable: true,
27586 enumerable: false,
27587 get: function() {
27588 return this.getAttribute('value') || '';
27589 },
27590 set: function(val) {
27591 this.setAttribute('value', val);
27592 }
27593 };
27594
27595 return {
27596 restrict: 'E',
27597 priority: 200,
27598 compile: function(_, attr) {
27599 if (lowercase(attr.type) !== 'hidden') {
27600 return;
27601 }
27602
27603 return {
27604 pre: function(scope, element, attr, ctrls) {
27605 var node = element[0];
27606
27607 // Support: Edge
27608 // Moving the DOM around prevents autofillling
27609 if (node.parentNode) {
27610 node.parentNode.insertBefore(node, node.nextSibling);
27611 }
27612
27613 // Support: FF, IE
27614 // Avoiding direct assignment to .value prevents autofillling
27615 if (Object.defineProperty) {
27616 Object.defineProperty(node, 'value', valueProperty);
27617 }
27618 }
27619 };
27620 }
27621 };
27622};
27623
27624
27625
27626var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
27627/**
27628 * @ngdoc directive
27629 * @name ngValue
27630 * @restrict A
27631 * @priority 100
27632 *
27633 * @description
27634 * Binds the given expression to the value of the element.
27635 *
27636 * It is mainly used on {@link input[radio] `input[radio]`} and option elements,
27637 * so that when the element is selected, the {@link ngModel `ngModel`} of that element (or its
27638 * {@link select `select`} parent element) is set to the bound value. It is especially useful
27639 * for dynamically generated lists using {@link ngRepeat `ngRepeat`}, as shown below.
27640 *
27641 * It can also be used to achieve one-way binding of a given expression to an input element
27642 * such as an `input[text]` or a `textarea`, when that element does not use ngModel.
27643 *
27644 * @element ANY
27645 * @param {string=} ngValue AngularJS expression, whose value will be bound to the `value` attribute
27646 * and `value` property of the element.
27647 *
27648 * @example
27649 <example name="ngValue-directive" module="valueExample">
27650 <file name="index.html">
27651 <script>
27652 angular.module('valueExample', [])
27653 .controller('ExampleController', ['$scope', function($scope) {
27654 $scope.names = ['pizza', 'unicorns', 'robots'];
27655 $scope.my = { favorite: 'unicorns' };
27656 }]);
27657 </script>
27658 <form ng-controller="ExampleController">
27659 <h2>Which is your favorite?</h2>
27660 <label ng-repeat="name in names" for="{{name}}">
27661 {{name}}
27662 <input type="radio"
27663 ng-model="my.favorite"
27664 ng-value="name"
27665 id="{{name}}"
27666 name="favorite">
27667 </label>
27668 <div>You chose {{my.favorite}}</div>
27669 </form>
27670 </file>
27671 <file name="protractor.js" type="protractor">
27672 var favorite = element(by.binding('my.favorite'));
27673
27674 it('should initialize to model', function() {
27675 expect(favorite.getText()).toContain('unicorns');
27676 });
27677 it('should bind the values to the inputs', function() {
27678 element.all(by.model('my.favorite')).get(0).click();
27679 expect(favorite.getText()).toContain('pizza');
27680 });
27681 </file>
27682 </example>
27683 */
27684var ngValueDirective = function() {
27685 /**
27686 * inputs use the value attribute as their default value if the value property is not set.
27687 * Once the value property has been set (by adding input), it will not react to changes to
27688 * the value attribute anymore. Setting both attribute and property fixes this behavior, and
27689 * makes it possible to use ngValue as a sort of one-way bind.
27690 */
27691 function updateElementValue(element, attr, value) {
27692 // Support: IE9 only
27693 // In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`).
27694 var propValue = isDefined(value) ? value : (msie === 9) ? '' : null;
27695 element.prop('value', propValue);
27696 attr.$set('value', value);
27697 }
27698
27699 return {
27700 restrict: 'A',
27701 priority: 100,
27702 compile: function(tpl, tplAttr) {
27703 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
27704 return function ngValueConstantLink(scope, elm, attr) {
27705 var value = scope.$eval(attr.ngValue);
27706 updateElementValue(elm, attr, value);
27707 };
27708 } else {
27709 return function ngValueLink(scope, elm, attr) {
27710 scope.$watch(attr.ngValue, function valueWatchAction(value) {
27711 updateElementValue(elm, attr, value);
27712 });
27713 };
27714 }
27715 }
27716 };
27717};
27718
27719/**
27720 * @ngdoc directive
27721 * @name ngBind
27722 * @restrict AC
27723 *
27724 * @description
27725 * The `ngBind` attribute tells AngularJS to replace the text content of the specified HTML element
27726 * with the value of a given expression, and to update the text content when the value of that
27727 * expression changes.
27728 *
27729 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
27730 * `{{ expression }}` which is similar but less verbose.
27731 *
27732 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
27733 * displayed by the browser in its raw state before AngularJS compiles it. Since `ngBind` is an
27734 * element attribute, it makes the bindings invisible to the user while the page is loading.
27735 *
27736 * An alternative solution to this problem would be using the
27737 * {@link ng.directive:ngCloak ngCloak} directive.
27738 *
27739 *
27740 * @element ANY
27741 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
27742 *
27743 * @example
27744 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
27745 <example module="bindExample" name="ng-bind">
27746 <file name="index.html">
27747 <script>
27748 angular.module('bindExample', [])
27749 .controller('ExampleController', ['$scope', function($scope) {
27750 $scope.name = 'Whirled';
27751 }]);
27752 </script>
27753 <div ng-controller="ExampleController">
27754 <label>Enter name: <input type="text" ng-model="name"></label><br>
27755 Hello <span ng-bind="name"></span>!
27756 </div>
27757 </file>
27758 <file name="protractor.js" type="protractor">
27759 it('should check ng-bind', function() {
27760 var nameInput = element(by.model('name'));
27761
27762 expect(element(by.binding('name')).getText()).toBe('Whirled');
27763 nameInput.clear();
27764 nameInput.sendKeys('world');
27765 expect(element(by.binding('name')).getText()).toBe('world');
27766 });
27767 </file>
27768 </example>
27769 */
27770var ngBindDirective = ['$compile', function($compile) {
27771 return {
27772 restrict: 'AC',
27773 compile: function ngBindCompile(templateElement) {
27774 $compile.$$addBindingClass(templateElement);
27775 return function ngBindLink(scope, element, attr) {
27776 $compile.$$addBindingInfo(element, attr.ngBind);
27777 element = element[0];
27778 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
27779 element.textContent = stringify(value);
27780 });
27781 };
27782 }
27783 };
27784}];
27785
27786
27787/**
27788 * @ngdoc directive
27789 * @name ngBindTemplate
27790 *
27791 * @description
27792 * The `ngBindTemplate` directive specifies that the element
27793 * text content should be replaced with the interpolation of the template
27794 * in the `ngBindTemplate` attribute.
27795 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
27796 * expressions. This directive is needed since some HTML elements
27797 * (such as TITLE and OPTION) cannot contain SPAN elements.
27798 *
27799 * @element ANY
27800 * @param {string} ngBindTemplate template of form
27801 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
27802 *
27803 * @example
27804 * Try it here: enter text in text box and watch the greeting change.
27805 <example module="bindExample" name="ng-bind-template">
27806 <file name="index.html">
27807 <script>
27808 angular.module('bindExample', [])
27809 .controller('ExampleController', ['$scope', function($scope) {
27810 $scope.salutation = 'Hello';
27811 $scope.name = 'World';
27812 }]);
27813 </script>
27814 <div ng-controller="ExampleController">
27815 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
27816 <label>Name: <input type="text" ng-model="name"></label><br>
27817 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
27818 </div>
27819 </file>
27820 <file name="protractor.js" type="protractor">
27821 it('should check ng-bind', function() {
27822 var salutationElem = element(by.binding('salutation'));
27823 var salutationInput = element(by.model('salutation'));
27824 var nameInput = element(by.model('name'));
27825
27826 expect(salutationElem.getText()).toBe('Hello World!');
27827
27828 salutationInput.clear();
27829 salutationInput.sendKeys('Greetings');
27830 nameInput.clear();
27831 nameInput.sendKeys('user');
27832
27833 expect(salutationElem.getText()).toBe('Greetings user!');
27834 });
27835 </file>
27836 </example>
27837 */
27838var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
27839 return {
27840 compile: function ngBindTemplateCompile(templateElement) {
27841 $compile.$$addBindingClass(templateElement);
27842 return function ngBindTemplateLink(scope, element, attr) {
27843 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
27844 $compile.$$addBindingInfo(element, interpolateFn.expressions);
27845 element = element[0];
27846 attr.$observe('ngBindTemplate', function(value) {
27847 element.textContent = isUndefined(value) ? '' : value;
27848 });
27849 };
27850 }
27851 };
27852}];
27853
27854
27855/**
27856 * @ngdoc directive
27857 * @name ngBindHtml
27858 *
27859 * @description
27860 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
27861 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
27862 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
27863 * ngSanitize} in your module's dependencies (not in core AngularJS). In order to use {@link ngSanitize}
27864 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
27865 *
27866 * You may also bypass sanitization for values you know are safe. To do so, bind to
27867 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
27868 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
27869 *
27870 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
27871 * will have an exception (instead of an exploit.)
27872 *
27873 * @element ANY
27874 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
27875 *
27876 * @example
27877
27878 <example module="bindHtmlExample" deps="angular-sanitize.js" name="ng-bind-html">
27879 <file name="index.html">
27880 <div ng-controller="ExampleController">
27881 <p ng-bind-html="myHTML"></p>
27882 </div>
27883 </file>
27884
27885 <file name="script.js">
27886 angular.module('bindHtmlExample', ['ngSanitize'])
27887 .controller('ExampleController', ['$scope', function($scope) {
27888 $scope.myHTML =
27889 'I am an <code>HTML</code>string with ' +
27890 '<a href="#">links!</a> and other <em>stuff</em>';
27891 }]);
27892 </file>
27893
27894 <file name="protractor.js" type="protractor">
27895 it('should check ng-bind-html', function() {
27896 expect(element(by.binding('myHTML')).getText()).toBe(
27897 'I am an HTMLstring with links! and other stuff');
27898 });
27899 </file>
27900 </example>
27901 */
27902var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
27903 return {
27904 restrict: 'A',
27905 compile: function ngBindHtmlCompile(tElement, tAttrs) {
27906 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
27907 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) {
27908 // Unwrap the value to compare the actual inner safe value, not the wrapper object.
27909 return $sce.valueOf(val);
27910 });
27911 $compile.$$addBindingClass(tElement);
27912
27913 return function ngBindHtmlLink(scope, element, attr) {
27914 $compile.$$addBindingInfo(element, attr.ngBindHtml);
27915
27916 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
27917 // The watched value is the unwrapped value. To avoid re-escaping, use the direct getter.
27918 var value = ngBindHtmlGetter(scope);
27919 element.html($sce.getTrustedHtml(value) || '');
27920 });
27921 };
27922 }
27923 };
27924}];
27925
27926/**
27927 * @ngdoc directive
27928 * @name ngChange
27929 * @restrict A
27930 *
27931 * @description
27932 * Evaluate the given expression when the user changes the input.
27933 * The expression is evaluated immediately, unlike the JavaScript onchange event
27934 * which only triggers at the end of a change (usually, when the user leaves the
27935 * form element or presses the return key).
27936 *
27937 * The `ngChange` expression is only evaluated when a change in the input value causes
27938 * a new value to be committed to the model.
27939 *
27940 * It will not be evaluated:
27941 * * if the value returned from the `$parsers` transformation pipeline has not changed
27942 * * if the input has continued to be invalid since the model will stay `null`
27943 * * if the model is changed programmatically and not by a change to the input value
27944 *
27945 *
27946 * Note, this directive requires `ngModel` to be present.
27947 *
27948 * @element ANY
27949 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
27950 * in input value.
27951 *
27952 * @example
27953 * <example name="ngChange-directive" module="changeExample">
27954 * <file name="index.html">
27955 * <script>
27956 * angular.module('changeExample', [])
27957 * .controller('ExampleController', ['$scope', function($scope) {
27958 * $scope.counter = 0;
27959 * $scope.change = function() {
27960 * $scope.counter++;
27961 * };
27962 * }]);
27963 * </script>
27964 * <div ng-controller="ExampleController">
27965 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
27966 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
27967 * <label for="ng-change-example2">Confirmed</label><br />
27968 * <tt>debug = {{confirmed}}</tt><br/>
27969 * <tt>counter = {{counter}}</tt><br/>
27970 * </div>
27971 * </file>
27972 * <file name="protractor.js" type="protractor">
27973 * var counter = element(by.binding('counter'));
27974 * var debug = element(by.binding('confirmed'));
27975 *
27976 * it('should evaluate the expression if changing from view', function() {
27977 * expect(counter.getText()).toContain('0');
27978 *
27979 * element(by.id('ng-change-example1')).click();
27980 *
27981 * expect(counter.getText()).toContain('1');
27982 * expect(debug.getText()).toContain('true');
27983 * });
27984 *
27985 * it('should not evaluate the expression if changing from model', function() {
27986 * element(by.id('ng-change-example2')).click();
27987
27988 * expect(counter.getText()).toContain('0');
27989 * expect(debug.getText()).toContain('true');
27990 * });
27991 * </file>
27992 * </example>
27993 */
27994var ngChangeDirective = valueFn({
27995 restrict: 'A',
27996 require: 'ngModel',
27997 link: function(scope, element, attr, ctrl) {
27998 ctrl.$viewChangeListeners.push(function() {
27999 scope.$eval(attr.ngChange);
28000 });
28001 }
28002});
28003
28004/* exported
28005 ngClassDirective,
28006 ngClassEvenDirective,
28007 ngClassOddDirective
28008*/
28009
28010function classDirective(name, selector) {
28011 name = 'ngClass' + name;
28012 var indexWatchExpression;
28013
28014 return ['$parse', function($parse) {
28015 return {
28016 restrict: 'AC',
28017 link: function(scope, element, attr) {
28018 var classCounts = element.data('$classCounts');
28019 var oldModulo = true;
28020 var oldClassString;
28021
28022 if (!classCounts) {
28023 // Use createMap() to prevent class assumptions involving property
28024 // names in Object.prototype
28025 classCounts = createMap();
28026 element.data('$classCounts', classCounts);
28027 }
28028
28029 if (name !== 'ngClass') {
28030 if (!indexWatchExpression) {
28031 indexWatchExpression = $parse('$index', function moduloTwo($index) {
28032 // eslint-disable-next-line no-bitwise
28033 return $index & 1;
28034 });
28035 }
28036
28037 scope.$watch(indexWatchExpression, ngClassIndexWatchAction);
28038 }
28039
28040 scope.$watch($parse(attr[name], toClassString), ngClassWatchAction);
28041
28042 function addClasses(classString) {
28043 classString = digestClassCounts(split(classString), 1);
28044 attr.$addClass(classString);
28045 }
28046
28047 function removeClasses(classString) {
28048 classString = digestClassCounts(split(classString), -1);
28049 attr.$removeClass(classString);
28050 }
28051
28052 function updateClasses(oldClassString, newClassString) {
28053 var oldClassArray = split(oldClassString);
28054 var newClassArray = split(newClassString);
28055
28056 var toRemoveArray = arrayDifference(oldClassArray, newClassArray);
28057 var toAddArray = arrayDifference(newClassArray, oldClassArray);
28058
28059 var toRemoveString = digestClassCounts(toRemoveArray, -1);
28060 var toAddString = digestClassCounts(toAddArray, 1);
28061
28062 attr.$addClass(toAddString);
28063 attr.$removeClass(toRemoveString);
28064 }
28065
28066 function digestClassCounts(classArray, count) {
28067 var classesToUpdate = [];
28068
28069 forEach(classArray, function(className) {
28070 if (count > 0 || classCounts[className]) {
28071 classCounts[className] = (classCounts[className] || 0) + count;
28072 if (classCounts[className] === +(count > 0)) {
28073 classesToUpdate.push(className);
28074 }
28075 }
28076 });
28077
28078 return classesToUpdate.join(' ');
28079 }
28080
28081 function ngClassIndexWatchAction(newModulo) {
28082 // This watch-action should run before the `ngClassWatchAction()`, thus it
28083 // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the
28084 // `ngClassWatchAction()` will update the classes.
28085 if (newModulo === selector) {
28086 addClasses(oldClassString);
28087 } else {
28088 removeClasses(oldClassString);
28089 }
28090
28091 oldModulo = newModulo;
28092 }
28093
28094 function ngClassWatchAction(newClassString) {
28095 if (oldModulo === selector) {
28096 updateClasses(oldClassString, newClassString);
28097 }
28098
28099 oldClassString = newClassString;
28100 }
28101 }
28102 };
28103 }];
28104
28105 // Helpers
28106 function arrayDifference(tokens1, tokens2) {
28107 if (!tokens1 || !tokens1.length) return [];
28108 if (!tokens2 || !tokens2.length) return tokens1;
28109
28110 var values = [];
28111
28112 outer:
28113 for (var i = 0; i < tokens1.length; i++) {
28114 var token = tokens1[i];
28115 for (var j = 0; j < tokens2.length; j++) {
28116 if (token === tokens2[j]) continue outer;
28117 }
28118 values.push(token);
28119 }
28120
28121 return values;
28122 }
28123
28124 function split(classString) {
28125 return classString && classString.split(' ');
28126 }
28127
28128 function toClassString(classValue) {
28129 if (!classValue) return classValue;
28130
28131 var classString = classValue;
28132
28133 if (isArray(classValue)) {
28134 classString = classValue.map(toClassString).join(' ');
28135 } else if (isObject(classValue)) {
28136 classString = Object.keys(classValue).
28137 filter(function(key) { return classValue[key]; }).
28138 join(' ');
28139 } else if (!isString(classValue)) {
28140 classString = classValue + '';
28141 }
28142
28143 return classString;
28144 }
28145}
28146
28147/**
28148 * @ngdoc directive
28149 * @name ngClass
28150 * @restrict AC
28151 * @element ANY
28152 *
28153 * @description
28154 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
28155 * an expression that represents all classes to be added.
28156 *
28157 * The directive operates in three different ways, depending on which of three types the expression
28158 * evaluates to:
28159 *
28160 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
28161 * names.
28162 *
28163 * 2. If the expression evaluates to an object, then for each key-value pair of the
28164 * object with a truthy value the corresponding key is used as a class name.
28165 *
28166 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
28167 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
28168 * to give you more control over what CSS classes appear. See the code below for an example of this.
28169 *
28170 *
28171 * The directive won't add duplicate classes if a particular class was already set.
28172 *
28173 * When the expression changes, the previously added classes are removed and only then are the
28174 * new classes added.
28175 *
28176 * @knownIssue
28177 * You should not use {@link guide/interpolation interpolation} in the value of the `class`
28178 * attribute, when using the `ngClass` directive on the same element.
28179 * See {@link guide/interpolation#known-issues here} for more info.
28180 *
28181 * @animations
28182 * | Animation | Occurs |
28183 * |----------------------------------|-------------------------------------|
28184 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
28185 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
28186 * | {@link ng.$animate#setClass setClass} | just before classes are added and classes are removed from the element at the same time |
28187 *
28188 * ### ngClass and pre-existing CSS3 Transitions/Animations
28189 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
28190 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
28191 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
28192 to view the step by step details of {@link $animate#addClass $animate.addClass} and
28193 {@link $animate#removeClass $animate.removeClass}.
28194 *
28195 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
28196 * of the evaluation can be a string representing space delimited class
28197 * names, an array, or a map of class names to boolean values. In the case of a map, the
28198 * names of the properties whose values are truthy will be added as css classes to the
28199 * element.
28200 *
28201 * @example
28202 * ### Basic
28203 <example name="ng-class">
28204 <file name="index.html">
28205 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
28206 <label>
28207 <input type="checkbox" ng-model="deleted">
28208 deleted (apply "strike" class)
28209 </label><br>
28210 <label>
28211 <input type="checkbox" ng-model="important">
28212 important (apply "bold" class)
28213 </label><br>
28214 <label>
28215 <input type="checkbox" ng-model="error">
28216 error (apply "has-error" class)
28217 </label>
28218 <hr>
28219 <p ng-class="style">Using String Syntax</p>
28220 <input type="text" ng-model="style"
28221 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
28222 <hr>
28223 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
28224 <input ng-model="style1"
28225 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
28226 <input ng-model="style2"
28227 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
28228 <input ng-model="style3"
28229 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
28230 <hr>
28231 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
28232 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
28233 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
28234 </file>
28235 <file name="style.css">
28236 .strike {
28237 text-decoration: line-through;
28238 }
28239 .bold {
28240 font-weight: bold;
28241 }
28242 .red {
28243 color: red;
28244 }
28245 .has-error {
28246 color: red;
28247 background-color: yellow;
28248 }
28249 .orange {
28250 color: orange;
28251 }
28252 </file>
28253 <file name="protractor.js" type="protractor">
28254 var ps = element.all(by.css('p'));
28255
28256 it('should let you toggle the class', function() {
28257
28258 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
28259 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
28260
28261 element(by.model('important')).click();
28262 expect(ps.first().getAttribute('class')).toMatch(/bold/);
28263
28264 element(by.model('error')).click();
28265 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
28266 });
28267
28268 it('should let you toggle string example', function() {
28269 expect(ps.get(1).getAttribute('class')).toBe('');
28270 element(by.model('style')).clear();
28271 element(by.model('style')).sendKeys('red');
28272 expect(ps.get(1).getAttribute('class')).toBe('red');
28273 });
28274
28275 it('array example should have 3 classes', function() {
28276 expect(ps.get(2).getAttribute('class')).toBe('');
28277 element(by.model('style1')).sendKeys('bold');
28278 element(by.model('style2')).sendKeys('strike');
28279 element(by.model('style3')).sendKeys('red');
28280 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
28281 });
28282
28283 it('array with map example should have 2 classes', function() {
28284 expect(ps.last().getAttribute('class')).toBe('');
28285 element(by.model('style4')).sendKeys('bold');
28286 element(by.model('warning')).click();
28287 expect(ps.last().getAttribute('class')).toBe('bold orange');
28288 });
28289 </file>
28290 </example>
28291
28292 @example
28293 ### Animations
28294
28295 The example below demonstrates how to perform animations using ngClass.
28296
28297 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class">
28298 <file name="index.html">
28299 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
28300 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
28301 <br>
28302 <span class="base-class" ng-class="myVar">Sample Text</span>
28303 </file>
28304 <file name="style.css">
28305 .base-class {
28306 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
28307 }
28308
28309 .base-class.my-class {
28310 color: red;
28311 font-size:3em;
28312 }
28313 </file>
28314 <file name="protractor.js" type="protractor">
28315 it('should check ng-class', function() {
28316 expect(element(by.css('.base-class')).getAttribute('class')).not.
28317 toMatch(/my-class/);
28318
28319 element(by.id('setbtn')).click();
28320
28321 expect(element(by.css('.base-class')).getAttribute('class')).
28322 toMatch(/my-class/);
28323
28324 element(by.id('clearbtn')).click();
28325
28326 expect(element(by.css('.base-class')).getAttribute('class')).not.
28327 toMatch(/my-class/);
28328 });
28329 </file>
28330 </example>
28331 */
28332var ngClassDirective = classDirective('', true);
28333
28334/**
28335 * @ngdoc directive
28336 * @name ngClassOdd
28337 * @restrict AC
28338 *
28339 * @description
28340 * The `ngClassOdd` and `ngClassEven` directives work exactly as
28341 * {@link ng.directive:ngClass ngClass}, except they work in
28342 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
28343 *
28344 * This directive can be applied only within the scope of an
28345 * {@link ng.directive:ngRepeat ngRepeat}.
28346 *
28347 * @animations
28348 * | Animation | Occurs |
28349 * |----------------------------------|-------------------------------------|
28350 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
28351 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
28352 *
28353 * @element ANY
28354 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
28355 * of the evaluation can be a string representing space delimited class names or an array.
28356 *
28357 * @example
28358 <example name="ng-class-odd">
28359 <file name="index.html">
28360 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
28361 <li ng-repeat="name in names">
28362 <span ng-class-odd="'odd'" ng-class-even="'even'">
28363 {{name}}
28364 </span>
28365 </li>
28366 </ol>
28367 </file>
28368 <file name="style.css">
28369 .odd {
28370 color: red;
28371 }
28372 .even {
28373 color: blue;
28374 }
28375 </file>
28376 <file name="protractor.js" type="protractor">
28377 it('should check ng-class-odd and ng-class-even', function() {
28378 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
28379 toMatch(/odd/);
28380 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
28381 toMatch(/even/);
28382 });
28383 </file>
28384 </example>
28385 *
28386 * <hr />
28387 * @example
28388 * An example on how to implement animations using `ngClassOdd`:
28389 *
28390 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class-odd-animate">
28391 <file name="index.html">
28392 <div ng-init="items=['Item 3', 'Item 2', 'Item 1', 'Item 0']">
28393 <button ng-click="items.unshift('Item ' + items.length)">Add item</button>
28394 <hr />
28395 <table>
28396 <tr ng-repeat="item in items" ng-class-odd="'odd'">
28397 <td>{{ item }}</td>
28398 </tr>
28399 </table>
28400 </div>
28401 </file>
28402 <file name="style.css">
28403 .odd {
28404 background: rgba(255, 255, 0, 0.25);
28405 }
28406
28407 .odd-add, .odd-remove {
28408 transition: 1.5s;
28409 }
28410 </file>
28411 <file name="protractor.js" type="protractor">
28412 it('should add new entries to the beginning of the list', function() {
28413 var button = element(by.buttonText('Add item'));
28414 var rows = element.all(by.repeater('item in items'));
28415
28416 expect(rows.count()).toBe(4);
28417 expect(rows.get(0).getText()).toBe('Item 3');
28418 expect(rows.get(1).getText()).toBe('Item 2');
28419
28420 button.click();
28421
28422 expect(rows.count()).toBe(5);
28423 expect(rows.get(0).getText()).toBe('Item 4');
28424 expect(rows.get(1).getText()).toBe('Item 3');
28425 });
28426
28427 it('should add odd class to odd entries', function() {
28428 var button = element(by.buttonText('Add item'));
28429 var rows = element.all(by.repeater('item in items'));
28430
28431 expect(rows.get(0).getAttribute('class')).toMatch(/odd/);
28432 expect(rows.get(1).getAttribute('class')).not.toMatch(/odd/);
28433
28434 button.click();
28435
28436 expect(rows.get(0).getAttribute('class')).toMatch(/odd/);
28437 expect(rows.get(1).getAttribute('class')).not.toMatch(/odd/);
28438 });
28439 </file>
28440 </example>
28441 */
28442var ngClassOddDirective = classDirective('Odd', 0);
28443
28444/**
28445 * @ngdoc directive
28446 * @name ngClassEven
28447 * @restrict AC
28448 *
28449 * @description
28450 * The `ngClassOdd` and `ngClassEven` directives work exactly as
28451 * {@link ng.directive:ngClass ngClass}, except they work in
28452 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
28453 *
28454 * This directive can be applied only within the scope of an
28455 * {@link ng.directive:ngRepeat ngRepeat}.
28456 *
28457 * @animations
28458 * | Animation | Occurs |
28459 * |----------------------------------|-------------------------------------|
28460 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
28461 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
28462 *
28463 * @element ANY
28464 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
28465 * result of the evaluation can be a string representing space delimited class names or an array.
28466 *
28467 * @example
28468 <example name="ng-class-even">
28469 <file name="index.html">
28470 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
28471 <li ng-repeat="name in names">
28472 <span ng-class-odd="'odd'" ng-class-even="'even'">
28473 {{name}} &nbsp; &nbsp; &nbsp;
28474 </span>
28475 </li>
28476 </ol>
28477 </file>
28478 <file name="style.css">
28479 .odd {
28480 color: red;
28481 }
28482 .even {
28483 color: blue;
28484 }
28485 </file>
28486 <file name="protractor.js" type="protractor">
28487 it('should check ng-class-odd and ng-class-even', function() {
28488 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
28489 toMatch(/odd/);
28490 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
28491 toMatch(/even/);
28492 });
28493 </file>
28494 </example>
28495 *
28496 * <hr />
28497 * @example
28498 * An example on how to implement animations using `ngClassEven`:
28499 *
28500 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class-even-animate">
28501 <file name="index.html">
28502 <div ng-init="items=['Item 3', 'Item 2', 'Item 1', 'Item 0']">
28503 <button ng-click="items.unshift('Item ' + items.length)">Add item</button>
28504 <hr />
28505 <table>
28506 <tr ng-repeat="item in items" ng-class-even="'even'">
28507 <td>{{ item }}</td>
28508 </tr>
28509 </table>
28510 </div>
28511 </file>
28512 <file name="style.css">
28513 .even {
28514 background: rgba(255, 255, 0, 0.25);
28515 }
28516
28517 .even-add, .even-remove {
28518 transition: 1.5s;
28519 }
28520 </file>
28521 <file name="protractor.js" type="protractor">
28522 it('should add new entries to the beginning of the list', function() {
28523 var button = element(by.buttonText('Add item'));
28524 var rows = element.all(by.repeater('item in items'));
28525
28526 expect(rows.count()).toBe(4);
28527 expect(rows.get(0).getText()).toBe('Item 3');
28528 expect(rows.get(1).getText()).toBe('Item 2');
28529
28530 button.click();
28531
28532 expect(rows.count()).toBe(5);
28533 expect(rows.get(0).getText()).toBe('Item 4');
28534 expect(rows.get(1).getText()).toBe('Item 3');
28535 });
28536
28537 it('should add even class to even entries', function() {
28538 var button = element(by.buttonText('Add item'));
28539 var rows = element.all(by.repeater('item in items'));
28540
28541 expect(rows.get(0).getAttribute('class')).not.toMatch(/even/);
28542 expect(rows.get(1).getAttribute('class')).toMatch(/even/);
28543
28544 button.click();
28545
28546 expect(rows.get(0).getAttribute('class')).not.toMatch(/even/);
28547 expect(rows.get(1).getAttribute('class')).toMatch(/even/);
28548 });
28549 </file>
28550 </example>
28551 */
28552var ngClassEvenDirective = classDirective('Even', 1);
28553
28554/**
28555 * @ngdoc directive
28556 * @name ngCloak
28557 * @restrict AC
28558 *
28559 * @description
28560 * The `ngCloak` directive is used to prevent the AngularJS html template from being briefly
28561 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
28562 * directive to avoid the undesirable flicker effect caused by the html template display.
28563 *
28564 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
28565 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
28566 * of the browser view.
28567 *
28568 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
28569 * `angular.min.js`.
28570 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
28571 *
28572 * ```css
28573 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
28574 * display: none !important;
28575 * }
28576 * ```
28577 *
28578 * When this css rule is loaded by the browser, all html elements (including their children) that
28579 * are tagged with the `ngCloak` directive are hidden. When AngularJS encounters this directive
28580 * during the compilation of the template it deletes the `ngCloak` element attribute, making
28581 * the compiled element visible.
28582 *
28583 * For the best result, the `angular.js` script must be loaded in the head section of the html
28584 * document; alternatively, the css rule above must be included in the external stylesheet of the
28585 * application.
28586 *
28587 * @element ANY
28588 *
28589 * @example
28590 <example name="ng-cloak">
28591 <file name="index.html">
28592 <div id="template1" ng-cloak>{{ 'hello' }}</div>
28593 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
28594 </file>
28595 <file name="protractor.js" type="protractor">
28596 it('should remove the template directive and css class', function() {
28597 expect($('#template1').getAttribute('ng-cloak')).
28598 toBeNull();
28599 expect($('#template2').getAttribute('ng-cloak')).
28600 toBeNull();
28601 });
28602 </file>
28603 </example>
28604 *
28605 */
28606var ngCloakDirective = ngDirective({
28607 compile: function(element, attr) {
28608 attr.$set('ngCloak', undefined);
28609 element.removeClass('ng-cloak');
28610 }
28611});
28612
28613/**
28614 * @ngdoc directive
28615 * @name ngController
28616 *
28617 * @description
28618 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
28619 * supports the principles behind the Model-View-Controller design pattern.
28620 *
28621 * MVC components in angular:
28622 *
28623 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
28624 * are accessed through bindings.
28625 * * View — The template (HTML with data bindings) that is rendered into the View.
28626 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
28627 * logic behind the application to decorate the scope with functions and values
28628 *
28629 * Note that you can also attach controllers to the DOM by declaring it in a route definition
28630 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
28631 * again using `ng-controller` in the template itself. This will cause the controller to be attached
28632 * and executed twice.
28633 *
28634 * @element ANY
28635 * @scope
28636 * @priority 500
28637 * @param {expression} ngController Name of a constructor function registered with the current
28638 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
28639 * that on the current scope evaluates to a constructor function.
28640 *
28641 * The controller instance can be published into a scope property by specifying
28642 * `ng-controller="as propertyName"`.
28643 *
28644 * @example
28645 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
28646 * greeting are methods declared on the controller (see source tab). These methods can
28647 * easily be called from the AngularJS markup. Any changes to the data are automatically reflected
28648 * in the View without the need for a manual update.
28649 *
28650 * Two different declaration styles are included below:
28651 *
28652 * * one binds methods and properties directly onto the controller using `this`:
28653 * `ng-controller="SettingsController1 as settings"`
28654 * * one injects `$scope` into the controller:
28655 * `ng-controller="SettingsController2"`
28656 *
28657 * The second option is more common in the AngularJS community, and is generally used in boilerplates
28658 * and in this guide. However, there are advantages to binding properties directly to the controller
28659 * and avoiding scope.
28660 *
28661 * * Using `controller as` makes it obvious which controller you are accessing in the template when
28662 * multiple controllers apply to an element.
28663 * * If you are writing your controllers as classes you have easier access to the properties and
28664 * methods, which will appear on the scope, from inside the controller code.
28665 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
28666 * inheritance masking primitives.
28667 *
28668 * This example demonstrates the `controller as` syntax.
28669 *
28670 * <example name="ngControllerAs" module="controllerAsExample">
28671 * <file name="index.html">
28672 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
28673 * <label>Name: <input type="text" ng-model="settings.name"/></label>
28674 * <button ng-click="settings.greet()">greet</button><br/>
28675 * Contact:
28676 * <ul>
28677 * <li ng-repeat="contact in settings.contacts">
28678 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
28679 * <option>phone</option>
28680 * <option>email</option>
28681 * </select>
28682 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
28683 * <button ng-click="settings.clearContact(contact)">clear</button>
28684 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
28685 * </li>
28686 * <li><button ng-click="settings.addContact()">add</button></li>
28687 * </ul>
28688 * </div>
28689 * </file>
28690 * <file name="app.js">
28691 * angular.module('controllerAsExample', [])
28692 * .controller('SettingsController1', SettingsController1);
28693 *
28694 * function SettingsController1() {
28695 * this.name = 'John Smith';
28696 * this.contacts = [
28697 * {type: 'phone', value: '408 555 1212'},
28698 * {type: 'email', value: 'john.smith@example.org'}
28699 * ];
28700 * }
28701 *
28702 * SettingsController1.prototype.greet = function() {
28703 * alert(this.name);
28704 * };
28705 *
28706 * SettingsController1.prototype.addContact = function() {
28707 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
28708 * };
28709 *
28710 * SettingsController1.prototype.removeContact = function(contactToRemove) {
28711 * var index = this.contacts.indexOf(contactToRemove);
28712 * this.contacts.splice(index, 1);
28713 * };
28714 *
28715 * SettingsController1.prototype.clearContact = function(contact) {
28716 * contact.type = 'phone';
28717 * contact.value = '';
28718 * };
28719 * </file>
28720 * <file name="protractor.js" type="protractor">
28721 * it('should check controller as', function() {
28722 * var container = element(by.id('ctrl-as-exmpl'));
28723 * expect(container.element(by.model('settings.name'))
28724 * .getAttribute('value')).toBe('John Smith');
28725 *
28726 * var firstRepeat =
28727 * container.element(by.repeater('contact in settings.contacts').row(0));
28728 * var secondRepeat =
28729 * container.element(by.repeater('contact in settings.contacts').row(1));
28730 *
28731 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
28732 * .toBe('408 555 1212');
28733 *
28734 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
28735 * .toBe('john.smith@example.org');
28736 *
28737 * firstRepeat.element(by.buttonText('clear')).click();
28738 *
28739 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
28740 * .toBe('');
28741 *
28742 * container.element(by.buttonText('add')).click();
28743 *
28744 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
28745 * .element(by.model('contact.value'))
28746 * .getAttribute('value'))
28747 * .toBe('yourname@example.org');
28748 * });
28749 * </file>
28750 * </example>
28751 *
28752 * This example demonstrates the "attach to `$scope`" style of controller.
28753 *
28754 * <example name="ngController" module="controllerExample">
28755 * <file name="index.html">
28756 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
28757 * <label>Name: <input type="text" ng-model="name"/></label>
28758 * <button ng-click="greet()">greet</button><br/>
28759 * Contact:
28760 * <ul>
28761 * <li ng-repeat="contact in contacts">
28762 * <select ng-model="contact.type" id="select_{{$index}}">
28763 * <option>phone</option>
28764 * <option>email</option>
28765 * </select>
28766 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
28767 * <button ng-click="clearContact(contact)">clear</button>
28768 * <button ng-click="removeContact(contact)">X</button>
28769 * </li>
28770 * <li>[ <button ng-click="addContact()">add</button> ]</li>
28771 * </ul>
28772 * </div>
28773 * </file>
28774 * <file name="app.js">
28775 * angular.module('controllerExample', [])
28776 * .controller('SettingsController2', ['$scope', SettingsController2]);
28777 *
28778 * function SettingsController2($scope) {
28779 * $scope.name = 'John Smith';
28780 * $scope.contacts = [
28781 * {type:'phone', value:'408 555 1212'},
28782 * {type:'email', value:'john.smith@example.org'}
28783 * ];
28784 *
28785 * $scope.greet = function() {
28786 * alert($scope.name);
28787 * };
28788 *
28789 * $scope.addContact = function() {
28790 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
28791 * };
28792 *
28793 * $scope.removeContact = function(contactToRemove) {
28794 * var index = $scope.contacts.indexOf(contactToRemove);
28795 * $scope.contacts.splice(index, 1);
28796 * };
28797 *
28798 * $scope.clearContact = function(contact) {
28799 * contact.type = 'phone';
28800 * contact.value = '';
28801 * };
28802 * }
28803 * </file>
28804 * <file name="protractor.js" type="protractor">
28805 * it('should check controller', function() {
28806 * var container = element(by.id('ctrl-exmpl'));
28807 *
28808 * expect(container.element(by.model('name'))
28809 * .getAttribute('value')).toBe('John Smith');
28810 *
28811 * var firstRepeat =
28812 * container.element(by.repeater('contact in contacts').row(0));
28813 * var secondRepeat =
28814 * container.element(by.repeater('contact in contacts').row(1));
28815 *
28816 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
28817 * .toBe('408 555 1212');
28818 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
28819 * .toBe('john.smith@example.org');
28820 *
28821 * firstRepeat.element(by.buttonText('clear')).click();
28822 *
28823 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
28824 * .toBe('');
28825 *
28826 * container.element(by.buttonText('add')).click();
28827 *
28828 * expect(container.element(by.repeater('contact in contacts').row(2))
28829 * .element(by.model('contact.value'))
28830 * .getAttribute('value'))
28831 * .toBe('yourname@example.org');
28832 * });
28833 * </file>
28834 *</example>
28835
28836 */
28837var ngControllerDirective = [function() {
28838 return {
28839 restrict: 'A',
28840 scope: true,
28841 controller: '@',
28842 priority: 500
28843 };
28844}];
28845
28846/**
28847 * @ngdoc directive
28848 * @name ngCsp
28849 *
28850 * @restrict A
28851 * @element ANY
28852 * @description
28853 *
28854 * AngularJS has some features that can conflict with certain restrictions that are applied when using
28855 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
28856 *
28857 * If you intend to implement CSP with these rules then you must tell AngularJS not to use these
28858 * features.
28859 *
28860 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
28861 *
28862 *
28863 * The following default rules in CSP affect AngularJS:
28864 *
28865 * * The use of `eval()`, `Function(string)` and similar functions to dynamically create and execute
28866 * code from strings is forbidden. AngularJS makes use of this in the {@link $parse} service to
28867 * provide a 30% increase in the speed of evaluating AngularJS expressions. (This CSP rule can be
28868 * disabled with the CSP keyword `unsafe-eval`, but it is generally not recommended as it would
28869 * weaken the protections offered by CSP.)
28870 *
28871 * * The use of inline resources, such as inline `<script>` and `<style>` elements, are forbidden.
28872 * This prevents apps from injecting custom styles directly into the document. AngularJS makes use of
28873 * this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). To make these
28874 * directives work when a CSP rule is blocking inline styles, you must link to the `angular-csp.css`
28875 * in your HTML manually. (This CSP rule can be disabled with the CSP keyword `unsafe-inline`, but
28876 * it is generally not recommended as it would weaken the protections offered by CSP.)
28877 *
28878 * If you do not provide `ngCsp` then AngularJS tries to autodetect if CSP is blocking dynamic code
28879 * creation from strings (e.g., `unsafe-eval` not specified in CSP header) and automatically
28880 * deactivates this feature in the {@link $parse} service. This autodetection, however, triggers a
28881 * CSP error to be logged in the console:
28882 *
28883 * ```
28884 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
28885 * script in the following Content Security Policy directive: "default-src 'self'". Note that
28886 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
28887 * ```
28888 *
28889 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
28890 * directive on an element of the HTML document that appears before the `<script>` tag that loads
28891 * the `angular.js` file.
28892 *
28893 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
28894 *
28895 * You can specify which of the CSP related AngularJS features should be deactivated by providing
28896 * a value for the `ng-csp` attribute. The options are as follows:
28897 *
28898 * * no-inline-style: this stops AngularJS from injecting CSS styles into the DOM
28899 *
28900 * * no-unsafe-eval: this stops AngularJS from optimizing $parse with unsafe eval of strings
28901 *
28902 * You can use these values in the following combinations:
28903 *
28904 *
28905 * * No declaration means that AngularJS will assume that you can do inline styles, but it will do
28906 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous
28907 * versions of AngularJS.
28908 *
28909 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell AngularJS to deactivate both inline
28910 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous
28911 * versions of AngularJS.
28912 *
28913 * * Specifying only `no-unsafe-eval` tells AngularJS that we must not use eval, but that we can
28914 * inject inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
28915 *
28916 * * Specifying only `no-inline-style` tells AngularJS that we must not inject styles, but that we can
28917 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
28918 *
28919 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells AngularJS that we must not inject
28920 * styles nor use eval, which is the same as an empty: ng-csp.
28921 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
28922 *
28923 * @example
28924 *
28925 * This example shows how to apply the `ngCsp` directive to the `html` tag.
28926 ```html
28927 <!doctype html>
28928 <html ng-app ng-csp>
28929 ...
28930 ...
28931 </html>
28932 ```
28933
28934 <!-- Note: the `.csp` suffix in the example name triggers CSP mode in our http server! -->
28935 <example name="example.csp" module="cspExample" ng-csp="true">
28936 <file name="index.html">
28937 <div ng-controller="MainController as ctrl">
28938 <div>
28939 <button ng-click="ctrl.inc()" id="inc">Increment</button>
28940 <span id="counter">
28941 {{ctrl.counter}}
28942 </span>
28943 </div>
28944
28945 <div>
28946 <button ng-click="ctrl.evil()" id="evil">Evil</button>
28947 <span id="evilError">
28948 {{ctrl.evilError}}
28949 </span>
28950 </div>
28951 </div>
28952 </file>
28953 <file name="script.js">
28954 angular.module('cspExample', [])
28955 .controller('MainController', function MainController() {
28956 this.counter = 0;
28957 this.inc = function() {
28958 this.counter++;
28959 };
28960 this.evil = function() {
28961 try {
28962 eval('1+2'); // eslint-disable-line no-eval
28963 } catch (e) {
28964 this.evilError = e.message;
28965 }
28966 };
28967 });
28968 </file>
28969 <file name="protractor.js" type="protractor">
28970 var util, webdriver;
28971
28972 var incBtn = element(by.id('inc'));
28973 var counter = element(by.id('counter'));
28974 var evilBtn = element(by.id('evil'));
28975 var evilError = element(by.id('evilError'));
28976
28977 function getAndClearSevereErrors() {
28978 return browser.manage().logs().get('browser').then(function(browserLog) {
28979 return browserLog.filter(function(logEntry) {
28980 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
28981 });
28982 });
28983 }
28984
28985 function clearErrors() {
28986 getAndClearSevereErrors();
28987 }
28988
28989 function expectNoErrors() {
28990 getAndClearSevereErrors().then(function(filteredLog) {
28991 expect(filteredLog.length).toEqual(0);
28992 if (filteredLog.length) {
28993 console.log('browser console errors: ' + util.inspect(filteredLog));
28994 }
28995 });
28996 }
28997
28998 function expectError(regex) {
28999 getAndClearSevereErrors().then(function(filteredLog) {
29000 var found = false;
29001 filteredLog.forEach(function(log) {
29002 if (log.message.match(regex)) {
29003 found = true;
29004 }
29005 });
29006 if (!found) {
29007 throw new Error('expected an error that matches ' + regex);
29008 }
29009 });
29010 }
29011
29012 beforeEach(function() {
29013 util = require('util');
29014 webdriver = require('selenium-webdriver');
29015 });
29016
29017 // For now, we only test on Chrome,
29018 // as Safari does not load the page with Protractor's injected scripts,
29019 // and Firefox webdriver always disables content security policy (#6358)
29020 if (browser.params.browser !== 'chrome') {
29021 return;
29022 }
29023
29024 it('should not report errors when the page is loaded', function() {
29025 // clear errors so we are not dependent on previous tests
29026 clearErrors();
29027 // Need to reload the page as the page is already loaded when
29028 // we come here
29029 browser.driver.getCurrentUrl().then(function(url) {
29030 browser.get(url);
29031 });
29032 expectNoErrors();
29033 });
29034
29035 it('should evaluate expressions', function() {
29036 expect(counter.getText()).toEqual('0');
29037 incBtn.click();
29038 expect(counter.getText()).toEqual('1');
29039 expectNoErrors();
29040 });
29041
29042 it('should throw and report an error when using "eval"', function() {
29043 evilBtn.click();
29044 expect(evilError.getText()).toMatch(/Content Security Policy/);
29045 expectError(/Content Security Policy/);
29046 });
29047 </file>
29048 </example>
29049 */
29050
29051// `ngCsp` is not implemented as a proper directive any more, because we need it be processed while
29052// we bootstrap the app (before `$parse` is instantiated). For this reason, we just have the `csp()`
29053// fn that looks for the `ng-csp` attribute anywhere in the current doc.
29054
29055/**
29056 * @ngdoc directive
29057 * @name ngClick
29058 * @restrict A
29059 * @element ANY
29060 * @priority 0
29061 *
29062 * @description
29063 * The ngClick directive allows you to specify custom behavior when
29064 * an element is clicked.
29065 *
29066 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
29067 * click. ({@link guide/expression#-event- Event object is available as `$event`})
29068 *
29069 * @example
29070 <example name="ng-click">
29071 <file name="index.html">
29072 <button ng-click="count = count + 1" ng-init="count=0">
29073 Increment
29074 </button>
29075 <span>
29076 count: {{count}}
29077 </span>
29078 </file>
29079 <file name="protractor.js" type="protractor">
29080 it('should check ng-click', function() {
29081 expect(element(by.binding('count')).getText()).toMatch('0');
29082 element(by.css('button')).click();
29083 expect(element(by.binding('count')).getText()).toMatch('1');
29084 });
29085 </file>
29086 </example>
29087 */
29088/*
29089 * A collection of directives that allows creation of custom event handlers that are defined as
29090 * AngularJS expressions and are compiled and executed within the current scope.
29091 */
29092var ngEventDirectives = {};
29093
29094// For events that might fire synchronously during DOM manipulation
29095// we need to execute their event handlers asynchronously using $evalAsync,
29096// so that they are not executed in an inconsistent state.
29097var forceAsyncEvents = {
29098 'blur': true,
29099 'focus': true
29100};
29101forEach(
29102 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
29103 function(eventName) {
29104 var directiveName = directiveNormalize('ng-' + eventName);
29105 ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
29106 return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]);
29107 }];
29108 }
29109);
29110
29111function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) {
29112 return {
29113 restrict: 'A',
29114 compile: function($element, attr) {
29115 // NOTE:
29116 // We expose the powerful `$event` object on the scope that provides access to the Window,
29117 // etc. This is OK, because expressions are not sandboxed any more (and the expression
29118 // sandbox was never meant to be a security feature anyway).
29119 var fn = $parse(attr[directiveName]);
29120 return function ngEventHandler(scope, element) {
29121 element.on(eventName, function(event) {
29122 var callback = function() {
29123 fn(scope, {$event: event});
29124 };
29125
29126 if (!$rootScope.$$phase) {
29127 scope.$apply(callback);
29128 } else if (forceAsync) {
29129 scope.$evalAsync(callback);
29130 } else {
29131 try {
29132 callback();
29133 } catch (error) {
29134 $exceptionHandler(error);
29135 }
29136 }
29137 });
29138 };
29139 }
29140 };
29141}
29142
29143/**
29144 * @ngdoc directive
29145 * @name ngDblclick
29146 * @restrict A
29147 * @element ANY
29148 * @priority 0
29149 *
29150 * @description
29151 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
29152 *
29153 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
29154 * a dblclick. (The Event object is available as `$event`)
29155 *
29156 * @example
29157 <example name="ng-dblclick">
29158 <file name="index.html">
29159 <button ng-dblclick="count = count + 1" ng-init="count=0">
29160 Increment (on double click)
29161 </button>
29162 count: {{count}}
29163 </file>
29164 </example>
29165 */
29166
29167
29168/**
29169 * @ngdoc directive
29170 * @name ngMousedown
29171 * @restrict A
29172 * @element ANY
29173 * @priority 0
29174 *
29175 * @description
29176 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
29177 *
29178 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
29179 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
29180 *
29181 * @example
29182 <example name="ng-mousedown">
29183 <file name="index.html">
29184 <button ng-mousedown="count = count + 1" ng-init="count=0">
29185 Increment (on mouse down)
29186 </button>
29187 count: {{count}}
29188 </file>
29189 </example>
29190 */
29191
29192
29193/**
29194 * @ngdoc directive
29195 * @name ngMouseup
29196 * @restrict A
29197 * @element ANY
29198 * @priority 0
29199 *
29200 * @description
29201 * Specify custom behavior on mouseup event.
29202 *
29203 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
29204 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
29205 *
29206 * @example
29207 <example name="ng-mouseup">
29208 <file name="index.html">
29209 <button ng-mouseup="count = count + 1" ng-init="count=0">
29210 Increment (on mouse up)
29211 </button>
29212 count: {{count}}
29213 </file>
29214 </example>
29215 */
29216
29217/**
29218 * @ngdoc directive
29219 * @name ngMouseover
29220 * @restrict A
29221 * @element ANY
29222 * @priority 0
29223 *
29224 * @description
29225 * Specify custom behavior on mouseover event.
29226 *
29227 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
29228 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
29229 *
29230 * @example
29231 <example name="ng-mouseover">
29232 <file name="index.html">
29233 <button ng-mouseover="count = count + 1" ng-init="count=0">
29234 Increment (when mouse is over)
29235 </button>
29236 count: {{count}}
29237 </file>
29238 </example>
29239 */
29240
29241
29242/**
29243 * @ngdoc directive
29244 * @name ngMouseenter
29245 * @restrict A
29246 * @element ANY
29247 * @priority 0
29248 *
29249 * @description
29250 * Specify custom behavior on mouseenter event.
29251 *
29252 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
29253 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
29254 *
29255 * @example
29256 <example name="ng-mouseenter">
29257 <file name="index.html">
29258 <button ng-mouseenter="count = count + 1" ng-init="count=0">
29259 Increment (when mouse enters)
29260 </button>
29261 count: {{count}}
29262 </file>
29263 </example>
29264 */
29265
29266
29267/**
29268 * @ngdoc directive
29269 * @name ngMouseleave
29270 * @restrict A
29271 * @element ANY
29272 * @priority 0
29273 *
29274 * @description
29275 * Specify custom behavior on mouseleave event.
29276 *
29277 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
29278 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
29279 *
29280 * @example
29281 <example name="ng-mouseleave">
29282 <file name="index.html">
29283 <button ng-mouseleave="count = count + 1" ng-init="count=0">
29284 Increment (when mouse leaves)
29285 </button>
29286 count: {{count}}
29287 </file>
29288 </example>
29289 */
29290
29291
29292/**
29293 * @ngdoc directive
29294 * @name ngMousemove
29295 * @restrict A
29296 * @element ANY
29297 * @priority 0
29298 *
29299 * @description
29300 * Specify custom behavior on mousemove event.
29301 *
29302 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
29303 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
29304 *
29305 * @example
29306 <example name="ng-mousemove">
29307 <file name="index.html">
29308 <button ng-mousemove="count = count + 1" ng-init="count=0">
29309 Increment (when mouse moves)
29310 </button>
29311 count: {{count}}
29312 </file>
29313 </example>
29314 */
29315
29316
29317/**
29318 * @ngdoc directive
29319 * @name ngKeydown
29320 * @restrict A
29321 * @element ANY
29322 * @priority 0
29323 *
29324 * @description
29325 * Specify custom behavior on keydown event.
29326 *
29327 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
29328 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
29329 *
29330 * @example
29331 <example name="ng-keydown">
29332 <file name="index.html">
29333 <input ng-keydown="count = count + 1" ng-init="count=0">
29334 key down count: {{count}}
29335 </file>
29336 </example>
29337 */
29338
29339
29340/**
29341 * @ngdoc directive
29342 * @name ngKeyup
29343 * @restrict A
29344 * @element ANY
29345 * @priority 0
29346 *
29347 * @description
29348 * Specify custom behavior on keyup event.
29349 *
29350 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
29351 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
29352 *
29353 * @example
29354 <example name="ng-keyup">
29355 <file name="index.html">
29356 <p>Typing in the input box below updates the key count</p>
29357 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
29358
29359 <p>Typing in the input box below updates the keycode</p>
29360 <input ng-keyup="event=$event">
29361 <p>event keyCode: {{ event.keyCode }}</p>
29362 <p>event altKey: {{ event.altKey }}</p>
29363 </file>
29364 </example>
29365 */
29366
29367
29368/**
29369 * @ngdoc directive
29370 * @name ngKeypress
29371 * @restrict A
29372 * @element ANY
29373 *
29374 * @description
29375 * Specify custom behavior on keypress event.
29376 *
29377 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
29378 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
29379 * and can be interrogated for keyCode, altKey, etc.)
29380 *
29381 * @example
29382 <example name="ng-keypress">
29383 <file name="index.html">
29384 <input ng-keypress="count = count + 1" ng-init="count=0">
29385 key press count: {{count}}
29386 </file>
29387 </example>
29388 */
29389
29390
29391/**
29392 * @ngdoc directive
29393 * @name ngSubmit
29394 * @restrict A
29395 * @element form
29396 * @priority 0
29397 *
29398 * @description
29399 * Enables binding AngularJS expressions to onsubmit events.
29400 *
29401 * Additionally it prevents the default action (which for form means sending the request to the
29402 * server and reloading the current page), but only if the form does not contain `action`,
29403 * `data-action`, or `x-action` attributes.
29404 *
29405 * <div class="alert alert-warning">
29406 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
29407 * `ngSubmit` handlers together. See the
29408 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
29409 * for a detailed discussion of when `ngSubmit` may be triggered.
29410 * </div>
29411 *
29412 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
29413 * ({@link guide/expression#-event- Event object is available as `$event`})
29414 *
29415 * @example
29416 <example module="submitExample" name="ng-submit">
29417 <file name="index.html">
29418 <script>
29419 angular.module('submitExample', [])
29420 .controller('ExampleController', ['$scope', function($scope) {
29421 $scope.list = [];
29422 $scope.text = 'hello';
29423 $scope.submit = function() {
29424 if ($scope.text) {
29425 $scope.list.push(this.text);
29426 $scope.text = '';
29427 }
29428 };
29429 }]);
29430 </script>
29431 <form ng-submit="submit()" ng-controller="ExampleController">
29432 Enter text and hit enter:
29433 <input type="text" ng-model="text" name="text" />
29434 <input type="submit" id="submit" value="Submit" />
29435 <pre>list={{list}}</pre>
29436 </form>
29437 </file>
29438 <file name="protractor.js" type="protractor">
29439 it('should check ng-submit', function() {
29440 expect(element(by.binding('list')).getText()).toBe('list=[]');
29441 element(by.css('#submit')).click();
29442 expect(element(by.binding('list')).getText()).toContain('hello');
29443 expect(element(by.model('text')).getAttribute('value')).toBe('');
29444 });
29445 it('should ignore empty strings', function() {
29446 expect(element(by.binding('list')).getText()).toBe('list=[]');
29447 element(by.css('#submit')).click();
29448 element(by.css('#submit')).click();
29449 expect(element(by.binding('list')).getText()).toContain('hello');
29450 });
29451 </file>
29452 </example>
29453 */
29454
29455/**
29456 * @ngdoc directive
29457 * @name ngFocus
29458 * @restrict A
29459 * @element window, input, select, textarea, a
29460 * @priority 0
29461 *
29462 * @description
29463 * Specify custom behavior on focus event.
29464 *
29465 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
29466 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
29467 * during an `$apply` to ensure a consistent state.
29468 *
29469 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
29470 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
29471 *
29472 * @example
29473 * See {@link ng.directive:ngClick ngClick}
29474 */
29475
29476/**
29477 * @ngdoc directive
29478 * @name ngBlur
29479 * @restrict A
29480 * @element window, input, select, textarea, a
29481 * @priority 0
29482 *
29483 * @description
29484 * Specify custom behavior on blur event.
29485 *
29486 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
29487 * an element has lost focus.
29488 *
29489 * Note: As the `blur` event is executed synchronously also during DOM manipulations
29490 * (e.g. removing a focussed input),
29491 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
29492 * during an `$apply` to ensure a consistent state.
29493 *
29494 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
29495 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
29496 *
29497 * @example
29498 * See {@link ng.directive:ngClick ngClick}
29499 */
29500
29501/**
29502 * @ngdoc directive
29503 * @name ngCopy
29504 * @restrict A
29505 * @element window, input, select, textarea, a
29506 * @priority 0
29507 *
29508 * @description
29509 * Specify custom behavior on copy event.
29510 *
29511 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
29512 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
29513 *
29514 * @example
29515 <example name="ng-copy">
29516 <file name="index.html">
29517 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
29518 copied: {{copied}}
29519 </file>
29520 </example>
29521 */
29522
29523/**
29524 * @ngdoc directive
29525 * @name ngCut
29526 * @restrict A
29527 * @element window, input, select, textarea, a
29528 * @priority 0
29529 *
29530 * @description
29531 * Specify custom behavior on cut event.
29532 *
29533 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
29534 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
29535 *
29536 * @example
29537 <example name="ng-cut">
29538 <file name="index.html">
29539 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
29540 cut: {{cut}}
29541 </file>
29542 </example>
29543 */
29544
29545/**
29546 * @ngdoc directive
29547 * @name ngPaste
29548 * @restrict A
29549 * @element window, input, select, textarea, a
29550 * @priority 0
29551 *
29552 * @description
29553 * Specify custom behavior on paste event.
29554 *
29555 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
29556 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
29557 *
29558 * @example
29559 <example name="ng-paste">
29560 <file name="index.html">
29561 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
29562 pasted: {{paste}}
29563 </file>
29564 </example>
29565 */
29566
29567/**
29568 * @ngdoc directive
29569 * @name ngIf
29570 * @restrict A
29571 * @multiElement
29572 *
29573 * @description
29574 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
29575 * {expression}. If the expression assigned to `ngIf` evaluates to a false
29576 * value then the element is removed from the DOM, otherwise a clone of the
29577 * element is reinserted into the DOM.
29578 *
29579 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
29580 * element in the DOM rather than changing its visibility via the `display` css property. A common
29581 * case when this difference is significant is when using css selectors that rely on an element's
29582 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
29583 *
29584 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
29585 * is created when the element is restored. The scope created within `ngIf` inherits from
29586 * its parent scope using
29587 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
29588 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
29589 * a javascript primitive defined in the parent scope. In this case any modifications made to the
29590 * variable within the child scope will override (hide) the value in the parent scope.
29591 *
29592 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
29593 * is if an element's class attribute is directly modified after it's compiled, using something like
29594 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
29595 * the added class will be lost because the original compiled state is used to regenerate the element.
29596 *
29597 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
29598 * and `leave` effects.
29599 *
29600 * @animations
29601 * | Animation | Occurs |
29602 * |----------------------------------|-------------------------------------|
29603 * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container |
29604 * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM |
29605 *
29606 * @element ANY
29607 * @scope
29608 * @priority 600
29609 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
29610 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
29611 * element is added to the DOM tree.
29612 *
29613 * @example
29614 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-if">
29615 <file name="index.html">
29616 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
29617 Show when checked:
29618 <span ng-if="checked" class="animate-if">
29619 This is removed when the checkbox is unchecked.
29620 </span>
29621 </file>
29622 <file name="animations.css">
29623 .animate-if {
29624 background:white;
29625 border:1px solid black;
29626 padding:10px;
29627 }
29628
29629 .animate-if.ng-enter, .animate-if.ng-leave {
29630 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
29631 }
29632
29633 .animate-if.ng-enter,
29634 .animate-if.ng-leave.ng-leave-active {
29635 opacity:0;
29636 }
29637
29638 .animate-if.ng-leave,
29639 .animate-if.ng-enter.ng-enter-active {
29640 opacity:1;
29641 }
29642 </file>
29643 </example>
29644 */
29645var ngIfDirective = ['$animate', '$compile', function($animate, $compile) {
29646 return {
29647 multiElement: true,
29648 transclude: 'element',
29649 priority: 600,
29650 terminal: true,
29651 restrict: 'A',
29652 $$tlb: true,
29653 link: function($scope, $element, $attr, ctrl, $transclude) {
29654 var block, childScope, previousElements;
29655 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
29656
29657 if (value) {
29658 if (!childScope) {
29659 $transclude(function(clone, newScope) {
29660 childScope = newScope;
29661 clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf);
29662 // Note: We only need the first/last node of the cloned nodes.
29663 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
29664 // by a directive with templateUrl when its template arrives.
29665 block = {
29666 clone: clone
29667 };
29668 $animate.enter(clone, $element.parent(), $element);
29669 });
29670 }
29671 } else {
29672 if (previousElements) {
29673 previousElements.remove();
29674 previousElements = null;
29675 }
29676 if (childScope) {
29677 childScope.$destroy();
29678 childScope = null;
29679 }
29680 if (block) {
29681 previousElements = getBlockNodes(block.clone);
29682 $animate.leave(previousElements).done(function(response) {
29683 if (response !== false) previousElements = null;
29684 });
29685 block = null;
29686 }
29687 }
29688 });
29689 }
29690 };
29691}];
29692
29693/**
29694 * @ngdoc directive
29695 * @name ngInclude
29696 * @restrict ECA
29697 * @scope
29698 * @priority -400
29699 *
29700 * @description
29701 * Fetches, compiles and includes an external HTML fragment.
29702 *
29703 * By default, the template URL is restricted to the same domain and protocol as the
29704 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
29705 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
29706 * you may either add them to your {@link ng.$sceDelegateProvider#trustedResourceUrlList trusted
29707 * resource URL list} or {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to
29708 * AngularJS's {@link ng.$sce Strict Contextual Escaping}.
29709 *
29710 * In addition, the browser's
29711 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
29712 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
29713 * policy may further restrict whether the template is successfully loaded.
29714 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
29715 * access on some browsers.
29716 *
29717 * @animations
29718 * | Animation | Occurs |
29719 * |----------------------------------|-------------------------------------|
29720 * | {@link ng.$animate#enter enter} | when the expression changes, on the new include |
29721 * | {@link ng.$animate#leave leave} | when the expression changes, on the old include |
29722 *
29723 * The enter and leave animation occur concurrently.
29724 *
29725 * @param {string} ngInclude|src AngularJS expression evaluating to URL. If the source is a string constant,
29726 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
29727 * @param {string=} onload Expression to evaluate when a new partial is loaded.
29728 * <div class="alert alert-warning">
29729 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
29730 * a function with the name on the window element, which will usually throw a
29731 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
29732 * different form that {@link guide/directive#normalization matches} `onload`.
29733 * </div>
29734 *
29735 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
29736 * $anchorScroll} to scroll the viewport after the content is loaded.
29737 *
29738 * - If the attribute is not set, disable scrolling.
29739 * - If the attribute is set without value, enable scrolling.
29740 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
29741 *
29742 * @example
29743 <example module="includeExample" deps="angular-animate.js" animations="true" name="ng-include">
29744 <file name="index.html">
29745 <div ng-controller="ExampleController">
29746 <select ng-model="template" ng-options="t.name for t in templates">
29747 <option value="">(blank)</option>
29748 </select>
29749 url of the template: <code>{{template.url}}</code>
29750 <hr/>
29751 <div class="slide-animate-container">
29752 <div class="slide-animate" ng-include="template.url"></div>
29753 </div>
29754 </div>
29755 </file>
29756 <file name="script.js">
29757 angular.module('includeExample', ['ngAnimate'])
29758 .controller('ExampleController', ['$scope', function($scope) {
29759 $scope.templates =
29760 [{ name: 'template1.html', url: 'template1.html'},
29761 { name: 'template2.html', url: 'template2.html'}];
29762 $scope.template = $scope.templates[0];
29763 }]);
29764 </file>
29765 <file name="template1.html">
29766 Content of template1.html
29767 </file>
29768 <file name="template2.html">
29769 Content of template2.html
29770 </file>
29771 <file name="animations.css">
29772 .slide-animate-container {
29773 position:relative;
29774 background:white;
29775 border:1px solid black;
29776 height:40px;
29777 overflow:hidden;
29778 }
29779
29780 .slide-animate {
29781 padding:10px;
29782 }
29783
29784 .slide-animate.ng-enter, .slide-animate.ng-leave {
29785 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
29786
29787 position:absolute;
29788 top:0;
29789 left:0;
29790 right:0;
29791 bottom:0;
29792 display:block;
29793 padding:10px;
29794 }
29795
29796 .slide-animate.ng-enter {
29797 top:-50px;
29798 }
29799 .slide-animate.ng-enter.ng-enter-active {
29800 top:0;
29801 }
29802
29803 .slide-animate.ng-leave {
29804 top:0;
29805 }
29806 .slide-animate.ng-leave.ng-leave-active {
29807 top:50px;
29808 }
29809 </file>
29810 <file name="protractor.js" type="protractor">
29811 var templateSelect = element(by.model('template'));
29812 var includeElem = element(by.css('[ng-include]'));
29813
29814 it('should load template1.html', function() {
29815 expect(includeElem.getText()).toMatch(/Content of template1.html/);
29816 });
29817
29818 it('should load template2.html', function() {
29819 if (browser.params.browser === 'firefox') {
29820 // Firefox can't handle using selects
29821 // See https://github.com/angular/protractor/issues/480
29822 return;
29823 }
29824 templateSelect.click();
29825 templateSelect.all(by.css('option')).get(2).click();
29826 expect(includeElem.getText()).toMatch(/Content of template2.html/);
29827 });
29828
29829 it('should change to blank', function() {
29830 if (browser.params.browser === 'firefox') {
29831 // Firefox can't handle using selects
29832 return;
29833 }
29834 templateSelect.click();
29835 templateSelect.all(by.css('option')).get(0).click();
29836 expect(includeElem.isPresent()).toBe(false);
29837 });
29838 </file>
29839 </example>
29840 */
29841
29842
29843/**
29844 * @ngdoc event
29845 * @name ngInclude#$includeContentRequested
29846 * @eventType emit on the scope ngInclude was declared in
29847 * @description
29848 * Emitted every time the ngInclude content is requested.
29849 *
29850 * @param {Object} angularEvent Synthetic event object.
29851 * @param {String} src URL of content to load.
29852 */
29853
29854
29855/**
29856 * @ngdoc event
29857 * @name ngInclude#$includeContentLoaded
29858 * @eventType emit on the current ngInclude scope
29859 * @description
29860 * Emitted every time the ngInclude content is reloaded.
29861 *
29862 * @param {Object} angularEvent Synthetic event object.
29863 * @param {String} src URL of content to load.
29864 */
29865
29866
29867/**
29868 * @ngdoc event
29869 * @name ngInclude#$includeContentError
29870 * @eventType emit on the scope ngInclude was declared in
29871 * @description
29872 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
29873 *
29874 * @param {Object} angularEvent Synthetic event object.
29875 * @param {String} src URL of content to load.
29876 */
29877var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
29878 function($templateRequest, $anchorScroll, $animate) {
29879 return {
29880 restrict: 'ECA',
29881 priority: 400,
29882 terminal: true,
29883 transclude: 'element',
29884 controller: angular.noop,
29885 compile: function(element, attr) {
29886 var srcExp = attr.ngInclude || attr.src,
29887 onloadExp = attr.onload || '',
29888 autoScrollExp = attr.autoscroll;
29889
29890 return function(scope, $element, $attr, ctrl, $transclude) {
29891 var changeCounter = 0,
29892 currentScope,
29893 previousElement,
29894 currentElement;
29895
29896 var cleanupLastIncludeContent = function() {
29897 if (previousElement) {
29898 previousElement.remove();
29899 previousElement = null;
29900 }
29901 if (currentScope) {
29902 currentScope.$destroy();
29903 currentScope = null;
29904 }
29905 if (currentElement) {
29906 $animate.leave(currentElement).done(function(response) {
29907 if (response !== false) previousElement = null;
29908 });
29909 previousElement = currentElement;
29910 currentElement = null;
29911 }
29912 };
29913
29914 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
29915 var afterAnimation = function(response) {
29916 if (response !== false && isDefined(autoScrollExp) &&
29917 (!autoScrollExp || scope.$eval(autoScrollExp))) {
29918 $anchorScroll();
29919 }
29920 };
29921 var thisChangeId = ++changeCounter;
29922
29923 if (src) {
29924 //set the 2nd param to true to ignore the template request error so that the inner
29925 //contents and scope can be cleaned up.
29926 $templateRequest(src, true).then(function(response) {
29927 if (scope.$$destroyed) return;
29928
29929 if (thisChangeId !== changeCounter) return;
29930 var newScope = scope.$new();
29931 ctrl.template = response;
29932
29933 // Note: This will also link all children of ng-include that were contained in the original
29934 // html. If that content contains controllers, ... they could pollute/change the scope.
29935 // However, using ng-include on an element with additional content does not make sense...
29936 // Note: We can't remove them in the cloneAttchFn of $transclude as that
29937 // function is called before linking the content, which would apply child
29938 // directives to non existing elements.
29939 var clone = $transclude(newScope, function(clone) {
29940 cleanupLastIncludeContent();
29941 $animate.enter(clone, null, $element).done(afterAnimation);
29942 });
29943
29944 currentScope = newScope;
29945 currentElement = clone;
29946
29947 currentScope.$emit('$includeContentLoaded', src);
29948 scope.$eval(onloadExp);
29949 }, function() {
29950 if (scope.$$destroyed) return;
29951
29952 if (thisChangeId === changeCounter) {
29953 cleanupLastIncludeContent();
29954 scope.$emit('$includeContentError', src);
29955 }
29956 });
29957 scope.$emit('$includeContentRequested', src);
29958 } else {
29959 cleanupLastIncludeContent();
29960 ctrl.template = null;
29961 }
29962 });
29963 };
29964 }
29965 };
29966}];
29967
29968// This directive is called during the $transclude call of the first `ngInclude` directive.
29969// It will replace and compile the content of the element with the loaded template.
29970// We need this directive so that the element content is already filled when
29971// the link function of another directive on the same element as ngInclude
29972// is called.
29973var ngIncludeFillContentDirective = ['$compile',
29974 function($compile) {
29975 return {
29976 restrict: 'ECA',
29977 priority: -400,
29978 require: 'ngInclude',
29979 link: function(scope, $element, $attr, ctrl) {
29980 if (toString.call($element[0]).match(/SVG/)) {
29981 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
29982 // support innerHTML, so detect this here and try to generate the contents
29983 // specially.
29984 $element.empty();
29985 $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope,
29986 function namespaceAdaptedClone(clone) {
29987 $element.append(clone);
29988 }, {futureParentElement: $element});
29989 return;
29990 }
29991
29992 $element.html(ctrl.template);
29993 $compile($element.contents())(scope);
29994 }
29995 };
29996 }];
29997
29998/**
29999 * @ngdoc directive
30000 * @name ngInit
30001 * @restrict AC
30002 * @priority 450
30003 * @element ANY
30004 *
30005 * @param {expression} ngInit {@link guide/expression Expression} to eval.
30006 *
30007 * @description
30008 * The `ngInit` directive allows you to evaluate an expression in the
30009 * current scope.
30010 *
30011 * <div class="alert alert-danger">
30012 * This directive can be abused to add unnecessary amounts of logic into your templates.
30013 * There are only a few appropriate uses of `ngInit`:
30014 * <ul>
30015 * <li>aliasing special properties of {@link ng.directive:ngRepeat `ngRepeat`},
30016 * as seen in the demo below.</li>
30017 * <li>initializing data during development, or for examples, as seen throughout these docs.</li>
30018 * <li>injecting data via server side scripting.</li>
30019 * </ul>
30020 *
30021 * Besides these few cases, you should use {@link guide/component Components} or
30022 * {@link guide/controller Controllers} rather than `ngInit` to initialize values on a scope.
30023 * </div>
30024 *
30025 * <div class="alert alert-warning">
30026 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
30027 * sure you have parentheses to ensure correct operator precedence:
30028 * <pre class="prettyprint">
30029 * `<div ng-init="test1 = ($index | toString)"></div>`
30030 * </pre>
30031 * </div>
30032 *
30033 * @example
30034 <example module="initExample" name="ng-init">
30035 <file name="index.html">
30036 <script>
30037 angular.module('initExample', [])
30038 .controller('ExampleController', ['$scope', function($scope) {
30039 $scope.list = [['a', 'b'], ['c', 'd']];
30040 }]);
30041 </script>
30042 <div ng-controller="ExampleController">
30043 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
30044 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
30045 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
30046 </div>
30047 </div>
30048 </div>
30049 </file>
30050 <file name="protractor.js" type="protractor">
30051 it('should alias index positions', function() {
30052 var elements = element.all(by.css('.example-init'));
30053 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
30054 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
30055 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
30056 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
30057 });
30058 </file>
30059 </example>
30060 */
30061var ngInitDirective = ngDirective({
30062 priority: 450,
30063 compile: function() {
30064 return {
30065 pre: function(scope, element, attrs) {
30066 scope.$eval(attrs.ngInit);
30067 }
30068 };
30069 }
30070});
30071
30072/**
30073 * @ngdoc directive
30074 * @name ngList
30075 * @restrict A
30076 * @priority 100
30077 *
30078 * @param {string=} ngList optional delimiter that should be used to split the value.
30079 *
30080 * @description
30081 * Text input that converts between a delimited string and an array of strings. The default
30082 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
30083 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
30084 *
30085 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
30086 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
30087 * list item is respected. This implies that the user of the directive is responsible for
30088 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
30089 * tab or newline character.
30090 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
30091 * when joining the list items back together) and whitespace around each list item is stripped
30092 * before it is added to the model.
30093 *
30094 * @example
30095 * ### Validation
30096 *
30097 * <example name="ngList-directive" module="listExample">
30098 * <file name="app.js">
30099 * angular.module('listExample', [])
30100 * .controller('ExampleController', ['$scope', function($scope) {
30101 * $scope.names = ['morpheus', 'neo', 'trinity'];
30102 * }]);
30103 * </file>
30104 * <file name="index.html">
30105 * <form name="myForm" ng-controller="ExampleController">
30106 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
30107 * <span role="alert">
30108 * <span class="error" ng-show="myForm.namesInput.$error.required">
30109 * Required!</span>
30110 * </span>
30111 * <br>
30112 * <tt>names = {{names}}</tt><br/>
30113 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
30114 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
30115 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
30116 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
30117 * </form>
30118 * </file>
30119 * <file name="protractor.js" type="protractor">
30120 * var listInput = element(by.model('names'));
30121 * var names = element(by.exactBinding('names'));
30122 * var valid = element(by.binding('myForm.namesInput.$valid'));
30123 * var error = element(by.css('span.error'));
30124 *
30125 * it('should initialize to model', function() {
30126 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
30127 * expect(valid.getText()).toContain('true');
30128 * expect(error.getCssValue('display')).toBe('none');
30129 * });
30130 *
30131 * it('should be invalid if empty', function() {
30132 * listInput.clear();
30133 * listInput.sendKeys('');
30134 *
30135 * expect(names.getText()).toContain('');
30136 * expect(valid.getText()).toContain('false');
30137 * expect(error.getCssValue('display')).not.toBe('none');
30138 * });
30139 * </file>
30140 * </example>
30141 *
30142 * @example
30143 * ### Splitting on newline
30144 *
30145 * <example name="ngList-directive-newlines">
30146 * <file name="index.html">
30147 * <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
30148 * <pre>{{ list | json }}</pre>
30149 * </file>
30150 * <file name="protractor.js" type="protractor">
30151 * it("should split the text by newlines", function() {
30152 * var listInput = element(by.model('list'));
30153 * var output = element(by.binding('list | json'));
30154 * listInput.sendKeys('abc\ndef\nghi');
30155 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
30156 * });
30157 * </file>
30158 * </example>
30159 *
30160 */
30161var ngListDirective = function() {
30162 return {
30163 restrict: 'A',
30164 priority: 100,
30165 require: 'ngModel',
30166 link: function(scope, element, attr, ctrl) {
30167 var ngList = attr.ngList || ', ';
30168 var trimValues = attr.ngTrim !== 'false';
30169 var separator = trimValues ? trim(ngList) : ngList;
30170
30171 var parse = function(viewValue) {
30172 // If the viewValue is invalid (say required but empty) it will be `undefined`
30173 if (isUndefined(viewValue)) return;
30174
30175 var list = [];
30176
30177 if (viewValue) {
30178 forEach(viewValue.split(separator), function(value) {
30179 if (value) list.push(trimValues ? trim(value) : value);
30180 });
30181 }
30182
30183 return list;
30184 };
30185
30186 ctrl.$parsers.push(parse);
30187 ctrl.$formatters.push(function(value) {
30188 if (isArray(value)) {
30189 return value.join(ngList);
30190 }
30191
30192 return undefined;
30193 });
30194
30195 // Override the standard $isEmpty because an empty array means the input is empty.
30196 ctrl.$isEmpty = function(value) {
30197 return !value || !value.length;
30198 };
30199 }
30200 };
30201};
30202
30203/* global VALID_CLASS: true,
30204 INVALID_CLASS: true,
30205 PRISTINE_CLASS: true,
30206 DIRTY_CLASS: true,
30207 UNTOUCHED_CLASS: true,
30208 TOUCHED_CLASS: true,
30209 PENDING_CLASS: true,
30210 addSetValidityMethod: true,
30211 setupValidity: true,
30212 defaultModelOptions: false
30213*/
30214
30215
30216var VALID_CLASS = 'ng-valid',
30217 INVALID_CLASS = 'ng-invalid',
30218 PRISTINE_CLASS = 'ng-pristine',
30219 DIRTY_CLASS = 'ng-dirty',
30220 UNTOUCHED_CLASS = 'ng-untouched',
30221 TOUCHED_CLASS = 'ng-touched',
30222 EMPTY_CLASS = 'ng-empty',
30223 NOT_EMPTY_CLASS = 'ng-not-empty';
30224
30225var ngModelMinErr = minErr('ngModel');
30226
30227/**
30228 * @ngdoc type
30229 * @name ngModel.NgModelController
30230 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
30231 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
30232 * is set.
30233 *
30234 * @property {*} $modelValue The value in the model that the control is bound to.
30235 *
30236 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
30237 * the control updates the ngModelController with a new {@link ngModel.NgModelController#$viewValue
30238 `$viewValue`} from the DOM, usually via user input.
30239 See {@link ngModel.NgModelController#$setViewValue `$setViewValue()`} for a detailed lifecycle explanation.
30240 Note that the `$parsers` are not called when the bound ngModel expression changes programmatically.
30241
30242 The functions are called in array order, each passing
30243 its return value through to the next. The last return value is forwarded to the
30244 {@link ngModel.NgModelController#$validators `$validators`} collection.
30245
30246 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
30247 `$viewValue`}.
30248
30249 Returning `undefined` from a parser means a parse error occurred. In that case,
30250 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
30251 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
30252 is set to `true`. The parse error is stored in `ngModel.$error.parse`.
30253
30254 This simple example shows a parser that would convert text input value to lowercase:
30255 * ```js
30256 * function parse(value) {
30257 * if (value) {
30258 * return value.toLowerCase();
30259 * }
30260 * }
30261 * ngModelController.$parsers.push(parse);
30262 * ```
30263
30264 *
30265 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
30266 the bound ngModel expression changes programmatically. The `$formatters` are not called when the
30267 value of the control is changed by user interaction.
30268
30269 Formatters are used to format / convert the {@link ngModel.NgModelController#$modelValue
30270 `$modelValue`} for display in the control.
30271
30272 The functions are called in reverse array order, each passing the value through to the
30273 next. The last return value is used as the actual DOM value.
30274
30275 This simple example shows a formatter that would convert the model value to uppercase:
30276
30277 * ```js
30278 * function format(value) {
30279 * if (value) {
30280 * return value.toUpperCase();
30281 * }
30282 * }
30283 * ngModel.$formatters.push(format);
30284 * ```
30285 *
30286 * @property {Object.<string, function>} $validators A collection of validators that are applied
30287 * whenever the model value changes. The key value within the object refers to the name of the
30288 * validator while the function refers to the validation operation. The validation operation is
30289 * provided with the model value as an argument and must return a true or false value depending
30290 * on the response of that validation.
30291 *
30292 * ```js
30293 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
30294 * var value = modelValue || viewValue;
30295 * return /[0-9]+/.test(value) &&
30296 * /[a-z]+/.test(value) &&
30297 * /[A-Z]+/.test(value) &&
30298 * /\W+/.test(value);
30299 * };
30300 * ```
30301 *
30302 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
30303 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
30304 * is expected to return a promise when it is run during the model validation process. Once the promise
30305 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
30306 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
30307 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
30308 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
30309 * will only run once all synchronous validators have passed.
30310 *
30311 * Please note that if $http is used then it is important that the server returns a success HTTP response code
30312 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
30313 *
30314 * ```js
30315 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
30316 * var value = modelValue || viewValue;
30317 *
30318 * // Lookup user by username
30319 * return $http.get('/api/users/' + value).
30320 * then(function resolved() {
30321 * //username exists, this means validation fails
30322 * return $q.reject('exists');
30323 * }, function rejected() {
30324 * //username does not exist, therefore this validation passes
30325 * return true;
30326 * });
30327 * };
30328 * ```
30329 *
30330 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever
30331 * a change to {@link ngModel.NgModelController#$viewValue `$viewValue`} has caused a change
30332 * to {@link ngModel.NgModelController#$modelValue `$modelValue`}.
30333 * It is called with no arguments, and its return value is ignored.
30334 * This can be used in place of additional $watches against the model value.
30335 *
30336 * @property {Object} $error An object hash with all failing validator ids as keys.
30337 * @property {Object} $pending An object hash with all pending validator ids as keys.
30338 *
30339 * @property {boolean} $untouched True if control has not lost focus yet.
30340 * @property {boolean} $touched True if control has lost focus.
30341 * @property {boolean} $pristine True if user has not interacted with the control yet.
30342 * @property {boolean} $dirty True if user has already interacted with the control.
30343 * @property {boolean} $valid True if there is no error.
30344 * @property {boolean} $invalid True if at least one error on the control.
30345 * @property {string} $name The name attribute of the control.
30346 *
30347 * @description
30348 *
30349 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
30350 * The controller contains services for data-binding, validation, CSS updates, and value formatting
30351 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
30352 * listening to DOM events.
30353 * Such DOM related logic should be provided by other directives which make use of
30354 * `NgModelController` for data-binding to control elements.
30355 * AngularJS provides this DOM logic for most {@link input `input`} elements.
30356 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
30357 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
30358 *
30359 * @example
30360 * ### Custom Control Example
30361 * This example shows how to use `NgModelController` with a custom control to achieve
30362 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
30363 * collaborate together to achieve the desired result.
30364 *
30365 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
30366 * contents be edited in place by the user.
30367 *
30368 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
30369 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
30370 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
30371 * that content using the `$sce` service.
30372 *
30373 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
30374 <file name="style.css">
30375 [contenteditable] {
30376 border: 1px solid black;
30377 background-color: white;
30378 min-height: 20px;
30379 }
30380
30381 .ng-invalid {
30382 border: 1px solid red;
30383 }
30384
30385 </file>
30386 <file name="script.js">
30387 angular.module('customControl', ['ngSanitize']).
30388 directive('contenteditable', ['$sce', function($sce) {
30389 return {
30390 restrict: 'A', // only activate on element attribute
30391 require: '?ngModel', // get a hold of NgModelController
30392 link: function(scope, element, attrs, ngModel) {
30393 if (!ngModel) return; // do nothing if no ng-model
30394
30395 // Specify how UI should be updated
30396 ngModel.$render = function() {
30397 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
30398 };
30399
30400 // Listen for change events to enable binding
30401 element.on('blur keyup change', function() {
30402 scope.$evalAsync(read);
30403 });
30404 read(); // initialize
30405
30406 // Write data to the model
30407 function read() {
30408 var html = element.html();
30409 // When we clear the content editable the browser leaves a <br> behind
30410 // If strip-br attribute is provided then we strip this out
30411 if (attrs.stripBr && html === '<br>') {
30412 html = '';
30413 }
30414 ngModel.$setViewValue(html);
30415 }
30416 }
30417 };
30418 }]);
30419 </file>
30420 <file name="index.html">
30421 <form name="myForm">
30422 <div contenteditable
30423 name="myWidget" ng-model="userContent"
30424 strip-br="true"
30425 required>Change me!</div>
30426 <span ng-show="myForm.myWidget.$error.required">Required!</span>
30427 <hr>
30428 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
30429 </form>
30430 </file>
30431 <file name="protractor.js" type="protractor">
30432 it('should data-bind and become invalid', function() {
30433 if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') {
30434 // SafariDriver can't handle contenteditable
30435 // and Firefox driver can't clear contenteditables very well
30436 return;
30437 }
30438 var contentEditable = element(by.css('[contenteditable]'));
30439 var content = 'Change me!';
30440
30441 expect(contentEditable.getText()).toEqual(content);
30442
30443 contentEditable.clear();
30444 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
30445 expect(contentEditable.getText()).toEqual('');
30446 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
30447 });
30448 </file>
30449 * </example>
30450 *
30451 *
30452 */
30453NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate'];
30454function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate) {
30455 this.$viewValue = Number.NaN;
30456 this.$modelValue = Number.NaN;
30457 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
30458 this.$validators = {};
30459 this.$asyncValidators = {};
30460 this.$parsers = [];
30461 this.$formatters = [];
30462 this.$viewChangeListeners = [];
30463 this.$untouched = true;
30464 this.$touched = false;
30465 this.$pristine = true;
30466 this.$dirty = false;
30467 this.$valid = true;
30468 this.$invalid = false;
30469 this.$error = {}; // keep invalid keys here
30470 this.$$success = {}; // keep valid keys here
30471 this.$pending = undefined; // keep pending keys here
30472 this.$name = $interpolate($attr.name || '', false)($scope);
30473 this.$$parentForm = nullFormCtrl;
30474 this.$options = defaultModelOptions;
30475 this.$$updateEvents = '';
30476 // Attach the correct context to the event handler function for updateOn
30477 this.$$updateEventHandler = this.$$updateEventHandler.bind(this);
30478
30479 this.$$parsedNgModel = $parse($attr.ngModel);
30480 this.$$parsedNgModelAssign = this.$$parsedNgModel.assign;
30481 this.$$ngModelGet = this.$$parsedNgModel;
30482 this.$$ngModelSet = this.$$parsedNgModelAssign;
30483 this.$$pendingDebounce = null;
30484 this.$$parserValid = undefined;
30485 this.$$parserName = 'parse';
30486
30487 this.$$currentValidationRunId = 0;
30488
30489 this.$$scope = $scope;
30490 this.$$rootScope = $scope.$root;
30491 this.$$attr = $attr;
30492 this.$$element = $element;
30493 this.$$animate = $animate;
30494 this.$$timeout = $timeout;
30495 this.$$parse = $parse;
30496 this.$$q = $q;
30497 this.$$exceptionHandler = $exceptionHandler;
30498
30499 setupValidity(this);
30500 setupModelWatcher(this);
30501}
30502
30503NgModelController.prototype = {
30504 $$initGetterSetters: function() {
30505 if (this.$options.getOption('getterSetter')) {
30506 var invokeModelGetter = this.$$parse(this.$$attr.ngModel + '()'),
30507 invokeModelSetter = this.$$parse(this.$$attr.ngModel + '($$$p)');
30508
30509 this.$$ngModelGet = function($scope) {
30510 var modelValue = this.$$parsedNgModel($scope);
30511 if (isFunction(modelValue)) {
30512 modelValue = invokeModelGetter($scope);
30513 }
30514 return modelValue;
30515 };
30516 this.$$ngModelSet = function($scope, newValue) {
30517 if (isFunction(this.$$parsedNgModel($scope))) {
30518 invokeModelSetter($scope, {$$$p: newValue});
30519 } else {
30520 this.$$parsedNgModelAssign($scope, newValue);
30521 }
30522 };
30523 } else if (!this.$$parsedNgModel.assign) {
30524 throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}',
30525 this.$$attr.ngModel, startingTag(this.$$element));
30526 }
30527 },
30528
30529
30530 /**
30531 * @ngdoc method
30532 * @name ngModel.NgModelController#$render
30533 *
30534 * @description
30535 * Called when the view needs to be updated. It is expected that the user of the ng-model
30536 * directive will implement this method.
30537 *
30538 * The `$render()` method is invoked in the following situations:
30539 *
30540 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
30541 * committed value then `$render()` is called to update the input control.
30542 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
30543 * the `$viewValue` are different from last time.
30544 *
30545 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
30546 * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue`
30547 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
30548 * invoked if you only change a property on the objects.
30549 */
30550 $render: noop,
30551
30552 /**
30553 * @ngdoc method
30554 * @name ngModel.NgModelController#$isEmpty
30555 *
30556 * @description
30557 * This is called when we need to determine if the value of an input is empty.
30558 *
30559 * For instance, the required directive does this to work out if the input has data or not.
30560 *
30561 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
30562 *
30563 * You can override this for input directives whose concept of being empty is different from the
30564 * default. The `checkboxInputType` directive does this because in its case a value of `false`
30565 * implies empty.
30566 *
30567 * @param {*} value The value of the input to check for emptiness.
30568 * @returns {boolean} True if `value` is "empty".
30569 */
30570 $isEmpty: function(value) {
30571 // eslint-disable-next-line no-self-compare
30572 return isUndefined(value) || value === '' || value === null || value !== value;
30573 },
30574
30575 $$updateEmptyClasses: function(value) {
30576 if (this.$isEmpty(value)) {
30577 this.$$animate.removeClass(this.$$element, NOT_EMPTY_CLASS);
30578 this.$$animate.addClass(this.$$element, EMPTY_CLASS);
30579 } else {
30580 this.$$animate.removeClass(this.$$element, EMPTY_CLASS);
30581 this.$$animate.addClass(this.$$element, NOT_EMPTY_CLASS);
30582 }
30583 },
30584
30585 /**
30586 * @ngdoc method
30587 * @name ngModel.NgModelController#$setPristine
30588 *
30589 * @description
30590 * Sets the control to its pristine state.
30591 *
30592 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
30593 * state (`ng-pristine` class). A model is considered to be pristine when the control
30594 * has not been changed from when first compiled.
30595 */
30596 $setPristine: function() {
30597 this.$dirty = false;
30598 this.$pristine = true;
30599 this.$$animate.removeClass(this.$$element, DIRTY_CLASS);
30600 this.$$animate.addClass(this.$$element, PRISTINE_CLASS);
30601 },
30602
30603 /**
30604 * @ngdoc method
30605 * @name ngModel.NgModelController#$setDirty
30606 *
30607 * @description
30608 * Sets the control to its dirty state.
30609 *
30610 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
30611 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
30612 * from when first compiled.
30613 */
30614 $setDirty: function() {
30615 this.$dirty = true;
30616 this.$pristine = false;
30617 this.$$animate.removeClass(this.$$element, PRISTINE_CLASS);
30618 this.$$animate.addClass(this.$$element, DIRTY_CLASS);
30619 this.$$parentForm.$setDirty();
30620 },
30621
30622 /**
30623 * @ngdoc method
30624 * @name ngModel.NgModelController#$setUntouched
30625 *
30626 * @description
30627 * Sets the control to its untouched state.
30628 *
30629 * This method can be called to remove the `ng-touched` class and set the control to its
30630 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
30631 * by default, however this function can be used to restore that state if the model has
30632 * already been touched by the user.
30633 */
30634 $setUntouched: function() {
30635 this.$touched = false;
30636 this.$untouched = true;
30637 this.$$animate.setClass(this.$$element, UNTOUCHED_CLASS, TOUCHED_CLASS);
30638 },
30639
30640 /**
30641 * @ngdoc method
30642 * @name ngModel.NgModelController#$setTouched
30643 *
30644 * @description
30645 * Sets the control to its touched state.
30646 *
30647 * This method can be called to remove the `ng-untouched` class and set the control to its
30648 * touched state (`ng-touched` class). A model is considered to be touched when the user has
30649 * first focused the control element and then shifted focus away from the control (blur event).
30650 */
30651 $setTouched: function() {
30652 this.$touched = true;
30653 this.$untouched = false;
30654 this.$$animate.setClass(this.$$element, TOUCHED_CLASS, UNTOUCHED_CLASS);
30655 },
30656
30657 /**
30658 * @ngdoc method
30659 * @name ngModel.NgModelController#$rollbackViewValue
30660 *
30661 * @description
30662 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
30663 * which may be caused by a pending debounced event or because the input is waiting for some
30664 * future event.
30665 *
30666 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that
30667 * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of
30668 * sync with the ngModel's `$modelValue`.
30669 *
30670 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update
30671 * and reset the input to the last committed view value.
30672 *
30673 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue`
30674 * programmatically before these debounced/future events have resolved/occurred, because AngularJS's
30675 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
30676 *
30677 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
30678 * input which may have such events pending. This is important in order to make sure that the
30679 * input field will be updated with the new model value and any pending operations are cancelled.
30680 *
30681 * @example
30682 * <example name="ng-model-cancel-update" module="cancel-update-example">
30683 * <file name="app.js">
30684 * angular.module('cancel-update-example', [])
30685 *
30686 * .controller('CancelUpdateController', ['$scope', function($scope) {
30687 * $scope.model = {value1: '', value2: ''};
30688 *
30689 * $scope.setEmpty = function(e, value, rollback) {
30690 * if (e.keyCode === 27) {
30691 * e.preventDefault();
30692 * if (rollback) {
30693 * $scope.myForm[value].$rollbackViewValue();
30694 * }
30695 * $scope.model[value] = '';
30696 * }
30697 * };
30698 * }]);
30699 * </file>
30700 * <file name="index.html">
30701 * <div ng-controller="CancelUpdateController">
30702 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should
30703 * empty them. Follow these steps and observe the difference:</p>
30704 * <ol>
30705 * <li>Type something in the input. You will see that the model is not yet updated</li>
30706 * <li>Press the Escape key.
30707 * <ol>
30708 * <li> In the first example, nothing happens, because the model is already '', and no
30709 * update is detected. If you blur the input, the model will be set to the current view.
30710 * </li>
30711 * <li> In the second example, the pending update is cancelled, and the input is set back
30712 * to the last committed view value (''). Blurring the input does nothing.
30713 * </li>
30714 * </ol>
30715 * </li>
30716 * </ol>
30717 *
30718 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
30719 * <div>
30720 * <p id="inputDescription1">Without $rollbackViewValue():</p>
30721 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1"
30722 * ng-keydown="setEmpty($event, 'value1')">
30723 * value1: "{{ model.value1 }}"
30724 * </div>
30725 *
30726 * <div>
30727 * <p id="inputDescription2">With $rollbackViewValue():</p>
30728 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2"
30729 * ng-keydown="setEmpty($event, 'value2', true)">
30730 * value2: "{{ model.value2 }}"
30731 * </div>
30732 * </form>
30733 * </div>
30734 * </file>
30735 <file name="style.css">
30736 div {
30737 display: table-cell;
30738 }
30739 div:nth-child(1) {
30740 padding-right: 30px;
30741 }
30742
30743 </file>
30744 * </example>
30745 */
30746 $rollbackViewValue: function() {
30747 this.$$timeout.cancel(this.$$pendingDebounce);
30748 this.$viewValue = this.$$lastCommittedViewValue;
30749 this.$render();
30750 },
30751
30752 /**
30753 * @ngdoc method
30754 * @name ngModel.NgModelController#$validate
30755 *
30756 * @description
30757 * Runs each of the registered validators (first synchronous validators and then
30758 * asynchronous validators).
30759 * If the validity changes to invalid, the model will be set to `undefined`,
30760 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
30761 * If the validity changes to valid, it will set the model to the last available valid
30762 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
30763 */
30764 $validate: function() {
30765
30766 // ignore $validate before model is initialized
30767 if (isNumberNaN(this.$modelValue)) {
30768 return;
30769 }
30770
30771 var viewValue = this.$$lastCommittedViewValue;
30772 // Note: we use the $$rawModelValue as $modelValue might have been
30773 // set to undefined during a view -> model update that found validation
30774 // errors. We can't parse the view here, since that could change
30775 // the model although neither viewValue nor the model on the scope changed
30776 var modelValue = this.$$rawModelValue;
30777
30778 var prevValid = this.$valid;
30779 var prevModelValue = this.$modelValue;
30780
30781 var allowInvalid = this.$options.getOption('allowInvalid');
30782
30783 var that = this;
30784 this.$$runValidators(modelValue, viewValue, function(allValid) {
30785 // If there was no change in validity, don't update the model
30786 // This prevents changing an invalid modelValue to undefined
30787 if (!allowInvalid && prevValid !== allValid) {
30788 // Note: Don't check this.$valid here, as we could have
30789 // external validators (e.g. calculated on the server),
30790 // that just call $setValidity and need the model value
30791 // to calculate their validity.
30792 that.$modelValue = allValid ? modelValue : undefined;
30793
30794 if (that.$modelValue !== prevModelValue) {
30795 that.$$writeModelToScope();
30796 }
30797 }
30798 });
30799 },
30800
30801 $$runValidators: function(modelValue, viewValue, doneCallback) {
30802 this.$$currentValidationRunId++;
30803 var localValidationRunId = this.$$currentValidationRunId;
30804 var that = this;
30805
30806 // check parser error
30807 if (!processParseErrors()) {
30808 validationDone(false);
30809 return;
30810 }
30811 if (!processSyncValidators()) {
30812 validationDone(false);
30813 return;
30814 }
30815 processAsyncValidators();
30816
30817 function processParseErrors() {
30818 var errorKey = that.$$parserName;
30819
30820 if (isUndefined(that.$$parserValid)) {
30821 setValidity(errorKey, null);
30822 } else {
30823 if (!that.$$parserValid) {
30824 forEach(that.$validators, function(v, name) {
30825 setValidity(name, null);
30826 });
30827 forEach(that.$asyncValidators, function(v, name) {
30828 setValidity(name, null);
30829 });
30830 }
30831
30832 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
30833 setValidity(errorKey, that.$$parserValid);
30834 return that.$$parserValid;
30835 }
30836 return true;
30837 }
30838
30839 function processSyncValidators() {
30840 var syncValidatorsValid = true;
30841 forEach(that.$validators, function(validator, name) {
30842 var result = Boolean(validator(modelValue, viewValue));
30843 syncValidatorsValid = syncValidatorsValid && result;
30844 setValidity(name, result);
30845 });
30846 if (!syncValidatorsValid) {
30847 forEach(that.$asyncValidators, function(v, name) {
30848 setValidity(name, null);
30849 });
30850 return false;
30851 }
30852 return true;
30853 }
30854
30855 function processAsyncValidators() {
30856 var validatorPromises = [];
30857 var allValid = true;
30858 forEach(that.$asyncValidators, function(validator, name) {
30859 var promise = validator(modelValue, viewValue);
30860 if (!isPromiseLike(promise)) {
30861 throw ngModelMinErr('nopromise',
30862 'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise);
30863 }
30864 setValidity(name, undefined);
30865 validatorPromises.push(promise.then(function() {
30866 setValidity(name, true);
30867 }, function() {
30868 allValid = false;
30869 setValidity(name, false);
30870 }));
30871 });
30872 if (!validatorPromises.length) {
30873 validationDone(true);
30874 } else {
30875 that.$$q.all(validatorPromises).then(function() {
30876 validationDone(allValid);
30877 }, noop);
30878 }
30879 }
30880
30881 function setValidity(name, isValid) {
30882 if (localValidationRunId === that.$$currentValidationRunId) {
30883 that.$setValidity(name, isValid);
30884 }
30885 }
30886
30887 function validationDone(allValid) {
30888 if (localValidationRunId === that.$$currentValidationRunId) {
30889
30890 doneCallback(allValid);
30891 }
30892 }
30893 },
30894
30895 /**
30896 * @ngdoc method
30897 * @name ngModel.NgModelController#$commitViewValue
30898 *
30899 * @description
30900 * Commit a pending update to the `$modelValue`.
30901 *
30902 * Updates may be pending by a debounced event or because the input is waiting for a some future
30903 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
30904 * usually handles calling this in response to input events.
30905 */
30906 $commitViewValue: function() {
30907 var viewValue = this.$viewValue;
30908
30909 this.$$timeout.cancel(this.$$pendingDebounce);
30910
30911 // If the view value has not changed then we should just exit, except in the case where there is
30912 // a native validator on the element. In this case the validation state may have changed even though
30913 // the viewValue has stayed empty.
30914 if (this.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !this.$$hasNativeValidators)) {
30915 return;
30916 }
30917 this.$$updateEmptyClasses(viewValue);
30918 this.$$lastCommittedViewValue = viewValue;
30919
30920 // change to dirty
30921 if (this.$pristine) {
30922 this.$setDirty();
30923 }
30924 this.$$parseAndValidate();
30925 },
30926
30927 $$parseAndValidate: function() {
30928 var viewValue = this.$$lastCommittedViewValue;
30929 var modelValue = viewValue;
30930 var that = this;
30931
30932 this.$$parserValid = isUndefined(modelValue) ? undefined : true;
30933
30934 // Reset any previous parse error
30935 this.$setValidity(this.$$parserName, null);
30936 this.$$parserName = 'parse';
30937
30938 if (this.$$parserValid) {
30939 for (var i = 0; i < this.$parsers.length; i++) {
30940 modelValue = this.$parsers[i](modelValue);
30941 if (isUndefined(modelValue)) {
30942 this.$$parserValid = false;
30943 break;
30944 }
30945 }
30946 }
30947 if (isNumberNaN(this.$modelValue)) {
30948 // this.$modelValue has not been touched yet...
30949 this.$modelValue = this.$$ngModelGet(this.$$scope);
30950 }
30951 var prevModelValue = this.$modelValue;
30952 var allowInvalid = this.$options.getOption('allowInvalid');
30953 this.$$rawModelValue = modelValue;
30954
30955 if (allowInvalid) {
30956 this.$modelValue = modelValue;
30957 writeToModelIfNeeded();
30958 }
30959
30960 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
30961 // This can happen if e.g. $setViewValue is called from inside a parser
30962 this.$$runValidators(modelValue, this.$$lastCommittedViewValue, function(allValid) {
30963 if (!allowInvalid) {
30964 // Note: Don't check this.$valid here, as we could have
30965 // external validators (e.g. calculated on the server),
30966 // that just call $setValidity and need the model value
30967 // to calculate their validity.
30968 that.$modelValue = allValid ? modelValue : undefined;
30969 writeToModelIfNeeded();
30970 }
30971 });
30972
30973 function writeToModelIfNeeded() {
30974 if (that.$modelValue !== prevModelValue) {
30975 that.$$writeModelToScope();
30976 }
30977 }
30978 },
30979
30980 $$writeModelToScope: function() {
30981 this.$$ngModelSet(this.$$scope, this.$modelValue);
30982 forEach(this.$viewChangeListeners, function(listener) {
30983 try {
30984 listener();
30985 } catch (e) {
30986 // eslint-disable-next-line no-invalid-this
30987 this.$$exceptionHandler(e);
30988 }
30989 }, this);
30990 },
30991
30992 /**
30993 * @ngdoc method
30994 * @name ngModel.NgModelController#$setViewValue
30995 *
30996 * @description
30997 * Update the view value.
30998 *
30999 * This method should be called when a control wants to change the view value; typically,
31000 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
31001 * directive calls it when the value of the input changes and {@link ng.directive:select select}
31002 * calls it when an option is selected.
31003 *
31004 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
31005 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
31006 * value is sent directly for processing through the `$parsers` pipeline. After this, the `$validators` and
31007 * `$asyncValidators` are called and the value is applied to `$modelValue`.
31008 * Finally, the value is set to the **expression** specified in the `ng-model` attribute and
31009 * all the registered change listeners, in the `$viewChangeListeners` list are called.
31010 *
31011 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
31012 * and the `default` trigger is not listed, all those actions will remain pending until one of the
31013 * `updateOn` events is triggered on the DOM element.
31014 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
31015 * directive is used with a custom debounce for this particular event.
31016 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
31017 * is specified, once the timer runs out.
31018 *
31019 * When used with standard inputs, the view value will always be a string (which is in some cases
31020 * parsed into another type, such as a `Date` object for `input[date]`.)
31021 * However, custom controls might also pass objects to this method. In this case, we should make
31022 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
31023 * perform a deep watch of objects, it only looks for a change of identity. If you only change
31024 * the property of the object then ngModel will not realize that the object has changed and
31025 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
31026 * not change properties of the copy once it has been passed to `$setViewValue`.
31027 * Otherwise you may cause the model value on the scope to change incorrectly.
31028 *
31029 * <div class="alert alert-info">
31030 * In any case, the value passed to the method should always reflect the current value
31031 * of the control. For example, if you are calling `$setViewValue` for an input element,
31032 * you should pass the input DOM value. Otherwise, the control and the scope model become
31033 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
31034 * the control's DOM value in any way. If we want to change the control's DOM value
31035 * programmatically, we should update the `ngModel` scope expression. Its new value will be
31036 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
31037 * to update the DOM, and finally call `$validate` on it.
31038 * </div>
31039 *
31040 * @param {*} value value from the view.
31041 * @param {string} trigger Event that triggered the update.
31042 */
31043 $setViewValue: function(value, trigger) {
31044 this.$viewValue = value;
31045 if (this.$options.getOption('updateOnDefault')) {
31046 this.$$debounceViewValueCommit(trigger);
31047 }
31048 },
31049
31050 $$debounceViewValueCommit: function(trigger) {
31051 var debounceDelay = this.$options.getOption('debounce');
31052
31053 if (isNumber(debounceDelay[trigger])) {
31054 debounceDelay = debounceDelay[trigger];
31055 } else if (isNumber(debounceDelay['default']) &&
31056 this.$options.getOption('updateOn').indexOf(trigger) === -1
31057 ) {
31058 debounceDelay = debounceDelay['default'];
31059 } else if (isNumber(debounceDelay['*'])) {
31060 debounceDelay = debounceDelay['*'];
31061 }
31062
31063 this.$$timeout.cancel(this.$$pendingDebounce);
31064 var that = this;
31065 if (debounceDelay > 0) { // this fails if debounceDelay is an object
31066 this.$$pendingDebounce = this.$$timeout(function() {
31067 that.$commitViewValue();
31068 }, debounceDelay);
31069 } else if (this.$$rootScope.$$phase) {
31070 this.$commitViewValue();
31071 } else {
31072 this.$$scope.$apply(function() {
31073 that.$commitViewValue();
31074 });
31075 }
31076 },
31077
31078 /**
31079 * @ngdoc method
31080 *
31081 * @name ngModel.NgModelController#$overrideModelOptions
31082 *
31083 * @description
31084 *
31085 * Override the current model options settings programmatically.
31086 *
31087 * The previous `ModelOptions` value will not be modified. Instead, a
31088 * new `ModelOptions` object will inherit from the previous one overriding
31089 * or inheriting settings that are defined in the given parameter.
31090 *
31091 * See {@link ngModelOptions} for information about what options can be specified
31092 * and how model option inheritance works.
31093 *
31094 * <div class="alert alert-warning">
31095 * **Note:** this function only affects the options set on the `ngModelController`,
31096 * and not the options on the {@link ngModelOptions} directive from which they might have been
31097 * obtained initially.
31098 * </div>
31099 *
31100 * <div class="alert alert-danger">
31101 * **Note:** it is not possible to override the `getterSetter` option.
31102 * </div>
31103 *
31104 * @param {Object} options a hash of settings to override the previous options
31105 *
31106 */
31107 $overrideModelOptions: function(options) {
31108 this.$options = this.$options.createChild(options);
31109 this.$$setUpdateOnEvents();
31110 },
31111
31112 /**
31113 * @ngdoc method
31114 *
31115 * @name ngModel.NgModelController#$processModelValue
31116
31117 * @description
31118 *
31119 * Runs the model -> view pipeline on the current
31120 * {@link ngModel.NgModelController#$modelValue $modelValue}.
31121 *
31122 * The following actions are performed by this method:
31123 *
31124 * - the `$modelValue` is run through the {@link ngModel.NgModelController#$formatters $formatters}
31125 * and the result is set to the {@link ngModel.NgModelController#$viewValue $viewValue}
31126 * - the `ng-empty` or `ng-not-empty` class is set on the element
31127 * - if the `$viewValue` has changed:
31128 * - {@link ngModel.NgModelController#$render $render} is called on the control
31129 * - the {@link ngModel.NgModelController#$validators $validators} are run and
31130 * the validation status is set.
31131 *
31132 * This method is called by ngModel internally when the bound scope value changes.
31133 * Application developers usually do not have to call this function themselves.
31134 *
31135 * This function can be used when the `$viewValue` or the rendered DOM value are not correctly
31136 * formatted and the `$modelValue` must be run through the `$formatters` again.
31137 *
31138 * @example
31139 * Consider a text input with an autocomplete list (for fruit), where the items are
31140 * objects with a name and an id.
31141 * A user enters `ap` and then selects `Apricot` from the list.
31142 * Based on this, the autocomplete widget will call `$setViewValue({name: 'Apricot', id: 443})`,
31143 * but the rendered value will still be `ap`.
31144 * The widget can then call `ctrl.$processModelValue()` to run the model -> view
31145 * pipeline again, which formats the object to the string `Apricot`,
31146 * then updates the `$viewValue`, and finally renders it in the DOM.
31147 *
31148 * <example module="inputExample" name="ng-model-process">
31149 <file name="index.html">
31150 <div ng-controller="inputController" style="display: flex;">
31151 <div style="margin-right: 30px;">
31152 Search Fruit:
31153 <basic-autocomplete items="items" on-select="selectedFruit = item"></basic-autocomplete>
31154 </div>
31155 <div>
31156 Model:<br>
31157 <pre>{{selectedFruit | json}}</pre>
31158 </div>
31159 </div>
31160 </file>
31161 <file name="app.js">
31162 angular.module('inputExample', [])
31163 .controller('inputController', function($scope) {
31164 $scope.items = [
31165 {name: 'Apricot', id: 443},
31166 {name: 'Clementine', id: 972},
31167 {name: 'Durian', id: 169},
31168 {name: 'Jackfruit', id: 982},
31169 {name: 'Strawberry', id: 863}
31170 ];
31171 })
31172 .component('basicAutocomplete', {
31173 bindings: {
31174 items: '<',
31175 onSelect: '&'
31176 },
31177 templateUrl: 'autocomplete.html',
31178 controller: function($element, $scope) {
31179 var that = this;
31180 var ngModel;
31181
31182 that.$postLink = function() {
31183 ngModel = $element.find('input').controller('ngModel');
31184
31185 ngModel.$formatters.push(function(value) {
31186 return (value && value.name) || value;
31187 });
31188
31189 ngModel.$parsers.push(function(value) {
31190 var match = value;
31191 for (var i = 0; i < that.items.length; i++) {
31192 if (that.items[i].name === value) {
31193 match = that.items[i];
31194 break;
31195 }
31196 }
31197
31198 return match;
31199 });
31200 };
31201
31202 that.selectItem = function(item) {
31203 ngModel.$setViewValue(item);
31204 ngModel.$processModelValue();
31205 that.onSelect({item: item});
31206 };
31207 }
31208 });
31209 </file>
31210 <file name="autocomplete.html">
31211 <div>
31212 <input type="search" ng-model="$ctrl.searchTerm" />
31213 <ul>
31214 <li ng-repeat="item in $ctrl.items | filter:$ctrl.searchTerm">
31215 <button ng-click="$ctrl.selectItem(item)">{{ item.name }}</button>
31216 </li>
31217 </ul>
31218 </div>
31219 </file>
31220 * </example>
31221 *
31222 */
31223 $processModelValue: function() {
31224 var viewValue = this.$$format();
31225
31226 if (this.$viewValue !== viewValue) {
31227 this.$$updateEmptyClasses(viewValue);
31228 this.$viewValue = this.$$lastCommittedViewValue = viewValue;
31229 this.$render();
31230 // It is possible that model and view value have been updated during render
31231 this.$$runValidators(this.$modelValue, this.$viewValue, noop);
31232 }
31233 },
31234
31235 /**
31236 * This method is called internally to run the $formatters on the $modelValue
31237 */
31238 $$format: function() {
31239 var formatters = this.$formatters,
31240 idx = formatters.length;
31241
31242 var viewValue = this.$modelValue;
31243 while (idx--) {
31244 viewValue = formatters[idx](viewValue);
31245 }
31246
31247 return viewValue;
31248 },
31249
31250 /**
31251 * This method is called internally when the bound scope value changes.
31252 */
31253 $$setModelValue: function(modelValue) {
31254 this.$modelValue = this.$$rawModelValue = modelValue;
31255 this.$$parserValid = undefined;
31256 this.$processModelValue();
31257 },
31258
31259 $$setUpdateOnEvents: function() {
31260 if (this.$$updateEvents) {
31261 this.$$element.off(this.$$updateEvents, this.$$updateEventHandler);
31262 }
31263
31264 this.$$updateEvents = this.$options.getOption('updateOn');
31265 if (this.$$updateEvents) {
31266 this.$$element.on(this.$$updateEvents, this.$$updateEventHandler);
31267 }
31268 },
31269
31270 $$updateEventHandler: function(ev) {
31271 this.$$debounceViewValueCommit(ev && ev.type);
31272 }
31273};
31274
31275function setupModelWatcher(ctrl) {
31276 // model -> value
31277 // Note: we cannot use a normal scope.$watch as we want to detect the following:
31278 // 1. scope value is 'a'
31279 // 2. user enters 'b'
31280 // 3. ng-change kicks in and reverts scope value to 'a'
31281 // -> scope value did not change since the last digest as
31282 // ng-change executes in apply phase
31283 // 4. view should be changed back to 'a'
31284 ctrl.$$scope.$watch(function ngModelWatch(scope) {
31285 var modelValue = ctrl.$$ngModelGet(scope);
31286
31287 // if scope model value and ngModel value are out of sync
31288 // This cannot be moved to the action function, because it would not catch the
31289 // case where the model is changed in the ngChange function or the model setter
31290 if (modelValue !== ctrl.$modelValue &&
31291 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
31292 // eslint-disable-next-line no-self-compare
31293 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
31294 ) {
31295 ctrl.$$setModelValue(modelValue);
31296 }
31297
31298 return modelValue;
31299 });
31300}
31301
31302/**
31303 * @ngdoc method
31304 * @name ngModel.NgModelController#$setValidity
31305 *
31306 * @description
31307 * Change the validity state, and notify the form.
31308 *
31309 * This method can be called within $parsers/$formatters or a custom validation implementation.
31310 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
31311 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
31312 *
31313 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
31314 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
31315 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
31316 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
31317 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
31318 * classes and can be bound to as `{{ someForm.someControl.$error.myError }}`.
31319 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
31320 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
31321 * Skipped is used by AngularJS when validators do not run because of parse errors and
31322 * when `$asyncValidators` do not run because any of the `$validators` failed.
31323 */
31324addSetValidityMethod({
31325 clazz: NgModelController,
31326 set: function(object, property) {
31327 object[property] = true;
31328 },
31329 unset: function(object, property) {
31330 delete object[property];
31331 }
31332});
31333
31334
31335/**
31336 * @ngdoc directive
31337 * @name ngModel
31338 * @restrict A
31339 * @priority 1
31340 * @param {expression} ngModel assignable {@link guide/expression Expression} to bind to.
31341 *
31342 * @description
31343 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
31344 * property on the scope using {@link ngModel.NgModelController NgModelController},
31345 * which is created and exposed by this directive.
31346 *
31347 * `ngModel` is responsible for:
31348 *
31349 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
31350 * require.
31351 * - Providing validation behavior (i.e. required, number, email, url).
31352 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
31353 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`,
31354 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations.
31355 * - Registering the control with its parent {@link ng.directive:form form}.
31356 *
31357 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
31358 * current scope. If the property doesn't already exist on this scope, it will be created
31359 * implicitly and added to the scope.
31360 *
31361 * For best practices on using `ngModel`, see:
31362 *
31363 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
31364 *
31365 * For basic examples, how to use `ngModel`, see:
31366 *
31367 * - {@link ng.directive:input input}
31368 * - {@link input[text] text}
31369 * - {@link input[checkbox] checkbox}
31370 * - {@link input[radio] radio}
31371 * - {@link input[number] number}
31372 * - {@link input[email] email}
31373 * - {@link input[url] url}
31374 * - {@link input[date] date}
31375 * - {@link input[datetime-local] datetime-local}
31376 * - {@link input[time] time}
31377 * - {@link input[month] month}
31378 * - {@link input[week] week}
31379 * - {@link ng.directive:select select}
31380 * - {@link ng.directive:textarea textarea}
31381 *
31382 * ## Complex Models (objects or collections)
31383 *
31384 * By default, `ngModel` watches the model by reference, not value. This is important to know when
31385 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the
31386 * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered.
31387 *
31388 * The model must be assigned an entirely new object or collection before a re-rendering will occur.
31389 *
31390 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression
31391 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or
31392 * if the select is given the `multiple` attribute.
31393 *
31394 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the
31395 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still
31396 * not trigger a re-rendering of the model.
31397 *
31398 * ## CSS classes
31399 * The following CSS classes are added and removed on the associated input/select/textarea element
31400 * depending on the validity of the model.
31401 *
31402 * - `ng-valid`: the model is valid
31403 * - `ng-invalid`: the model is invalid
31404 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
31405 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
31406 * - `ng-pristine`: the control hasn't been interacted with yet
31407 * - `ng-dirty`: the control has been interacted with
31408 * - `ng-touched`: the control has been blurred
31409 * - `ng-untouched`: the control hasn't been blurred
31410 * - `ng-pending`: any `$asyncValidators` are unfulfilled
31411 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined
31412 * by the {@link ngModel.NgModelController#$isEmpty} method
31413 * - `ng-not-empty`: the view contains a non-empty value
31414 *
31415 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
31416 *
31417 * @animations
31418 * Animations within models are triggered when any of the associated CSS classes are added and removed
31419 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`,
31420 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
31421 * The animations that are triggered within ngModel are similar to how they work in ngClass and
31422 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
31423 *
31424 * The following example shows a simple way to utilize CSS transitions to style an input element
31425 * that has been rendered as invalid after it has been validated:
31426 *
31427 * <pre>
31428 * //be sure to include ngAnimate as a module to hook into more
31429 * //advanced animations
31430 * .my-input {
31431 * transition:0.5s linear all;
31432 * background: white;
31433 * }
31434 * .my-input.ng-invalid {
31435 * background: red;
31436 * color:white;
31437 * }
31438 * </pre>
31439 *
31440 * @example
31441 * ### Basic Usage
31442 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample" name="ng-model">
31443 <file name="index.html">
31444 <script>
31445 angular.module('inputExample', [])
31446 .controller('ExampleController', ['$scope', function($scope) {
31447 $scope.val = '1';
31448 }]);
31449 </script>
31450 <style>
31451 .my-input {
31452 transition:all linear 0.5s;
31453 background: transparent;
31454 }
31455 .my-input.ng-invalid {
31456 color:white;
31457 background: red;
31458 }
31459 </style>
31460 <p id="inputDescription">
31461 Update input to see transitions when valid/invalid.
31462 Integer is a valid value.
31463 </p>
31464 <form name="testForm" ng-controller="ExampleController">
31465 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
31466 aria-describedby="inputDescription" />
31467 </form>
31468 </file>
31469 * </example>
31470 *
31471 * @example
31472 * ### Binding to a getter/setter
31473 *
31474 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
31475 * function that returns a representation of the model when called with zero arguments, and sets
31476 * the internal state of a model when called with an argument. It's sometimes useful to use this
31477 * for models that have an internal representation that's different from what the model exposes
31478 * to the view.
31479 *
31480 * <div class="alert alert-success">
31481 * **Best Practice:** It's best to keep getters fast because AngularJS is likely to call them more
31482 * frequently than other parts of your code.
31483 * </div>
31484 *
31485 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
31486 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
31487 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
31488 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
31489 *
31490 * The following example shows how to use `ngModel` with a getter/setter:
31491 *
31492 * @example
31493 * <example name="ngModel-getter-setter" module="getterSetterExample">
31494 <file name="index.html">
31495 <div ng-controller="ExampleController">
31496 <form name="userForm">
31497 <label>Name:
31498 <input type="text" name="userName"
31499 ng-model="user.name"
31500 ng-model-options="{ getterSetter: true }" />
31501 </label>
31502 </form>
31503 <pre>user.name = <span ng-bind="user.name()"></span></pre>
31504 </div>
31505 </file>
31506 <file name="app.js">
31507 angular.module('getterSetterExample', [])
31508 .controller('ExampleController', ['$scope', function($scope) {
31509 var _name = 'Brian';
31510 $scope.user = {
31511 name: function(newName) {
31512 // Note that newName can be undefined for two reasons:
31513 // 1. Because it is called as a getter and thus called with no arguments
31514 // 2. Because the property should actually be set to undefined. This happens e.g. if the
31515 // input is invalid
31516 return arguments.length ? (_name = newName) : _name;
31517 }
31518 };
31519 }]);
31520 </file>
31521 * </example>
31522 */
31523var ngModelDirective = ['$rootScope', function($rootScope) {
31524 return {
31525 restrict: 'A',
31526 require: ['ngModel', '^?form', '^?ngModelOptions'],
31527 controller: NgModelController,
31528 // Prelink needs to run before any input directive
31529 // so that we can set the NgModelOptions in NgModelController
31530 // before anyone else uses it.
31531 priority: 1,
31532 compile: function ngModelCompile(element) {
31533 // Setup initial state of the control
31534 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
31535
31536 return {
31537 pre: function ngModelPreLink(scope, element, attr, ctrls) {
31538 var modelCtrl = ctrls[0],
31539 formCtrl = ctrls[1] || modelCtrl.$$parentForm,
31540 optionsCtrl = ctrls[2];
31541
31542 if (optionsCtrl) {
31543 modelCtrl.$options = optionsCtrl.$options;
31544 }
31545
31546 modelCtrl.$$initGetterSetters();
31547
31548 // notify others, especially parent forms
31549 formCtrl.$addControl(modelCtrl);
31550
31551 attr.$observe('name', function(newValue) {
31552 if (modelCtrl.$name !== newValue) {
31553 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
31554 }
31555 });
31556
31557 scope.$on('$destroy', function() {
31558 modelCtrl.$$parentForm.$removeControl(modelCtrl);
31559 });
31560 },
31561 post: function ngModelPostLink(scope, element, attr, ctrls) {
31562 var modelCtrl = ctrls[0];
31563 modelCtrl.$$setUpdateOnEvents();
31564
31565 function setTouched() {
31566 modelCtrl.$setTouched();
31567 }
31568
31569 element.on('blur', function() {
31570 if (modelCtrl.$touched) return;
31571
31572 if ($rootScope.$$phase) {
31573 scope.$evalAsync(setTouched);
31574 } else {
31575 scope.$apply(setTouched);
31576 }
31577 });
31578 }
31579 };
31580 }
31581 };
31582}];
31583
31584/* exported defaultModelOptions */
31585var defaultModelOptions;
31586var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
31587
31588/**
31589 * @ngdoc type
31590 * @name ModelOptions
31591 * @description
31592 * A container for the options set by the {@link ngModelOptions} directive
31593 */
31594function ModelOptions(options) {
31595 this.$$options = options;
31596}
31597
31598ModelOptions.prototype = {
31599
31600 /**
31601 * @ngdoc method
31602 * @name ModelOptions#getOption
31603 * @param {string} name the name of the option to retrieve
31604 * @returns {*} the value of the option
31605 * @description
31606 * Returns the value of the given option
31607 */
31608 getOption: function(name) {
31609 return this.$$options[name];
31610 },
31611
31612 /**
31613 * @ngdoc method
31614 * @name ModelOptions#createChild
31615 * @param {Object} options a hash of options for the new child that will override the parent's options
31616 * @return {ModelOptions} a new `ModelOptions` object initialized with the given options.
31617 */
31618 createChild: function(options) {
31619 var inheritAll = false;
31620
31621 // make a shallow copy
31622 options = extend({}, options);
31623
31624 // Inherit options from the parent if specified by the value `"$inherit"`
31625 forEach(options, /** @this */ function(option, key) {
31626 if (option === '$inherit') {
31627 if (key === '*') {
31628 inheritAll = true;
31629 } else {
31630 options[key] = this.$$options[key];
31631 // `updateOn` is special so we must also inherit the `updateOnDefault` option
31632 if (key === 'updateOn') {
31633 options.updateOnDefault = this.$$options.updateOnDefault;
31634 }
31635 }
31636 } else {
31637 if (key === 'updateOn') {
31638 // If the `updateOn` property contains the `default` event then we have to remove
31639 // it from the event list and set the `updateOnDefault` flag.
31640 options.updateOnDefault = false;
31641 options[key] = trim(option.replace(DEFAULT_REGEXP, function() {
31642 options.updateOnDefault = true;
31643 return ' ';
31644 }));
31645 }
31646 }
31647 }, this);
31648
31649 if (inheritAll) {
31650 // We have a property of the form: `"*": "$inherit"`
31651 delete options['*'];
31652 defaults(options, this.$$options);
31653 }
31654
31655 // Finally add in any missing defaults
31656 defaults(options, defaultModelOptions.$$options);
31657
31658 return new ModelOptions(options);
31659 }
31660};
31661
31662
31663defaultModelOptions = new ModelOptions({
31664 updateOn: '',
31665 updateOnDefault: true,
31666 debounce: 0,
31667 getterSetter: false,
31668 allowInvalid: false,
31669 timezone: null
31670});
31671
31672
31673/**
31674 * @ngdoc directive
31675 * @name ngModelOptions
31676 * @restrict A
31677 * @priority 10
31678 *
31679 * @description
31680 * This directive allows you to modify the behaviour of {@link ngModel} directives within your
31681 * application. You can specify an `ngModelOptions` directive on any element. All {@link ngModel}
31682 * directives will use the options of their nearest `ngModelOptions` ancestor.
31683 *
31684 * The `ngModelOptions` settings are found by evaluating the value of the attribute directive as
31685 * an AngularJS expression. This expression should evaluate to an object, whose properties contain
31686 * the settings. For example: `<div ng-model-options="{ debounce: 100 }"`.
31687 *
31688 * ## Inheriting Options
31689 *
31690 * You can specify that an `ngModelOptions` setting should be inherited from a parent `ngModelOptions`
31691 * directive by giving it the value of `"$inherit"`.
31692 * Then it will inherit that setting from the first `ngModelOptions` directive found by traversing up the
31693 * DOM tree. If there is no ancestor element containing an `ngModelOptions` directive then default settings
31694 * will be used.
31695 *
31696 * For example given the following fragment of HTML
31697 *
31698 *
31699 * ```html
31700 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }">
31701 * <form ng-model-options="{ updateOn: 'blur', allowInvalid: '$inherit' }">
31702 * <input ng-model-options="{ updateOn: 'default', allowInvalid: '$inherit' }" />
31703 * </form>
31704 * </div>
31705 * ```
31706 *
31707 * the `input` element will have the following settings
31708 *
31709 * ```js
31710 * { allowInvalid: true, updateOn: 'default', debounce: 0 }
31711 * ```
31712 *
31713 * Notice that the `debounce` setting was not inherited and used the default value instead.
31714 *
31715 * You can specify that all undefined settings are automatically inherited from an ancestor by
31716 * including a property with key of `"*"` and value of `"$inherit"`.
31717 *
31718 * For example given the following fragment of HTML
31719 *
31720 *
31721 * ```html
31722 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }">
31723 * <form ng-model-options="{ updateOn: 'blur', "*": '$inherit' }">
31724 * <input ng-model-options="{ updateOn: 'default', "*": '$inherit' }" />
31725 * </form>
31726 * </div>
31727 * ```
31728 *
31729 * the `input` element will have the following settings
31730 *
31731 * ```js
31732 * { allowInvalid: true, updateOn: 'default', debounce: 200 }
31733 * ```
31734 *
31735 * Notice that the `debounce` setting now inherits the value from the outer `<div>` element.
31736 *
31737 * If you are creating a reusable component then you should be careful when using `"*": "$inherit"`
31738 * since you may inadvertently inherit a setting in the future that changes the behavior of your component.
31739 *
31740 *
31741 * ## Triggering and debouncing model updates
31742 *
31743 * The `updateOn` and `debounce` properties allow you to specify a custom list of events that will
31744 * trigger a model update and/or a debouncing delay so that the actual update only takes place when
31745 * a timer expires; this timer will be reset after another change takes place.
31746 *
31747 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
31748 * be different from the value in the actual model. This means that if you update the model you
31749 * should also invoke {@link ngModel.NgModelController#$rollbackViewValue} on the relevant input field in
31750 * order to make sure it is synchronized with the model and that any debounced action is canceled.
31751 *
31752 * The easiest way to reference the control's {@link ngModel.NgModelController#$rollbackViewValue}
31753 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
31754 * important because `form` controllers are published to the related scope under the name in their
31755 * `name` attribute.
31756 *
31757 * Any pending changes will take place immediately when an enclosing form is submitted via the
31758 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
31759 * to have access to the updated model.
31760 *
31761 * ### Overriding immediate updates
31762 *
31763 * The following example shows how to override immediate updates. Changes on the inputs within the
31764 * form will update the model only when the control loses focus (blur event). If `escape` key is
31765 * pressed while the input field is focused, the value is reset to the value in the current model.
31766 *
31767 * <example name="ngModelOptions-directive-blur" module="optionsExample">
31768 * <file name="index.html">
31769 * <div ng-controller="ExampleController">
31770 * <form name="userForm">
31771 * <label>
31772 * Name:
31773 * <input type="text" name="userName"
31774 * ng-model="user.name"
31775 * ng-model-options="{ updateOn: 'blur' }"
31776 * ng-keyup="cancel($event)" />
31777 * </label><br />
31778 * <label>
31779 * Other data:
31780 * <input type="text" ng-model="user.data" />
31781 * </label><br />
31782 * </form>
31783 * <pre>user.name = <span ng-bind="user.name"></span></pre>
31784 * </div>
31785 * </file>
31786 * <file name="app.js">
31787 * angular.module('optionsExample', [])
31788 * .controller('ExampleController', ['$scope', function($scope) {
31789 * $scope.user = { name: 'say', data: '' };
31790 *
31791 * $scope.cancel = function(e) {
31792 * if (e.keyCode === 27) {
31793 * $scope.userForm.userName.$rollbackViewValue();
31794 * }
31795 * };
31796 * }]);
31797 * </file>
31798 * <file name="protractor.js" type="protractor">
31799 * var model = element(by.binding('user.name'));
31800 * var input = element(by.model('user.name'));
31801 * var other = element(by.model('user.data'));
31802 *
31803 * it('should allow custom events', function() {
31804 * input.sendKeys(' hello');
31805 * input.click();
31806 * expect(model.getText()).toEqual('say');
31807 * other.click();
31808 * expect(model.getText()).toEqual('say hello');
31809 * });
31810 *
31811 * it('should $rollbackViewValue when model changes', function() {
31812 * input.sendKeys(' hello');
31813 * expect(input.getAttribute('value')).toEqual('say hello');
31814 * input.sendKeys(protractor.Key.ESCAPE);
31815 * expect(input.getAttribute('value')).toEqual('say');
31816 * other.click();
31817 * expect(model.getText()).toEqual('say');
31818 * });
31819 * </file>
31820 * </example>
31821 *
31822 * ### Debouncing updates
31823 *
31824 * The next example shows how to debounce model changes. Model will be updated only 1 sec after last change.
31825 * If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
31826 *
31827 * <example name="ngModelOptions-directive-debounce" module="optionsExample">
31828 * <file name="index.html">
31829 * <div ng-controller="ExampleController">
31830 * <form name="userForm">
31831 * Name:
31832 * <input type="text" name="userName"
31833 * ng-model="user.name"
31834 * ng-model-options="{ debounce: 1000 }" />
31835 * <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
31836 * </form>
31837 * <pre>user.name = <span ng-bind="user.name"></span></pre>
31838 * </div>
31839 * </file>
31840 * <file name="app.js">
31841 * angular.module('optionsExample', [])
31842 * .controller('ExampleController', ['$scope', function($scope) {
31843 * $scope.user = { name: 'say' };
31844 * }]);
31845 * </file>
31846 * </example>
31847 *
31848 * ### Default events, extra triggers, and catch-all debounce values
31849 *
31850 * This example shows the relationship between "default" update events and
31851 * additional `updateOn` triggers.
31852 *
31853 * `default` events are those that are bound to the control, and when fired, update the `$viewValue`
31854 * via {@link ngModel.NgModelController#$setViewValue $setViewValue}. Every event that is not listed
31855 * in `updateOn` is considered a "default" event, since different control types have different
31856 * default events.
31857 *
31858 * The control in this example updates by "default", "click", and "blur", with different `debounce`
31859 * values. You can see that "click" doesn't have an individual `debounce` value -
31860 * therefore it uses the `*` debounce value.
31861 *
31862 * There is also a button that calls {@link ngModel.NgModelController#$setViewValue $setViewValue}
31863 * directly with a "custom" event. Since "custom" is not defined in the `updateOn` list,
31864 * it is considered a "default" event and will update the
31865 * control if "default" is defined in `updateOn`, and will receive the "default" debounce value.
31866 * Note that this is just to illustrate how custom controls would possibly call `$setViewValue`.
31867 *
31868 * You can change the `updateOn` and `debounce` configuration to test different scenarios. This
31869 * is done with {@link ngModel.NgModelController#$overrideModelOptions $overrideModelOptions}.
31870 *
31871 <example name="ngModelOptions-advanced" module="optionsExample">
31872 <file name="index.html">
31873 <model-update-demo></model-update-demo>
31874 </file>
31875 <file name="app.js">
31876 angular.module('optionsExample', [])
31877 .component('modelUpdateDemo', {
31878 templateUrl: 'template.html',
31879 controller: function() {
31880 this.name = 'Chinua';
31881
31882 this.options = {
31883 updateOn: 'default blur click',
31884 debounce: {
31885 default: 2000,
31886 blur: 0,
31887 '*': 1000
31888 }
31889 };
31890
31891 this.updateEvents = function() {
31892 var eventList = this.options.updateOn.split(' ');
31893 eventList.push('*');
31894 var events = {};
31895
31896 for (var i = 0; i < eventList.length; i++) {
31897 events[eventList[i]] = this.options.debounce[eventList[i]];
31898 }
31899
31900 this.events = events;
31901 };
31902
31903 this.updateOptions = function() {
31904 var options = angular.extend(this.options, {
31905 updateOn: Object.keys(this.events).join(' ').replace('*', ''),
31906 debounce: this.events
31907 });
31908
31909 this.form.input.$overrideModelOptions(options);
31910 };
31911
31912 // Initialize the event form
31913 this.updateEvents();
31914 }
31915 });
31916 </file>
31917 <file name="template.html">
31918 <form name="$ctrl.form">
31919 Input: <input type="text" name="input" ng-model="$ctrl.name" ng-model-options="$ctrl.options" />
31920 </form>
31921 Model: <tt>{{$ctrl.name}}</tt>
31922 <hr>
31923 <button ng-click="$ctrl.form.input.$setViewValue('some value', 'custom')">Trigger setViewValue with 'some value' and 'custom' event</button>
31924
31925 <hr>
31926 <form ng-submit="$ctrl.updateOptions()">
31927 <b>updateOn</b><br>
31928 <input type="text" ng-model="$ctrl.options.updateOn" ng-change="$ctrl.updateEvents()" ng-model-options="{debounce: 500}">
31929
31930 <table>
31931 <tr>
31932 <th>Option</th>
31933 <th>Debounce value</th>
31934 </tr>
31935 <tr ng-repeat="(key, value) in $ctrl.events">
31936 <td>{{key}}</td>
31937 <td><input type="number" ng-model="$ctrl.events[key]" /></td>
31938 </tr>
31939 </table>
31940
31941 <br>
31942 <input type="submit" value="Update options">
31943 </form>
31944 </file>
31945 </example>
31946 *
31947 *
31948 * ## Model updates and validation
31949 *
31950 * The default behaviour in `ngModel` is that the model value is set to `undefined` when the
31951 * validation determines that the value is invalid. By setting the `allowInvalid` property to true,
31952 * the model will still be updated even if the value is invalid.
31953 *
31954 *
31955 * ## Connecting to the scope
31956 *
31957 * By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression
31958 * on the scope refers to a "getter/setter" function rather than the value itself.
31959 *
31960 * The following example shows how to bind to getter/setters:
31961 *
31962 * <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
31963 * <file name="index.html">
31964 * <div ng-controller="ExampleController">
31965 * <form name="userForm">
31966 * <label>
31967 * Name:
31968 * <input type="text" name="userName"
31969 * ng-model="user.name"
31970 * ng-model-options="{ getterSetter: true }" />
31971 * </label>
31972 * </form>
31973 * <pre>user.name = <span ng-bind="user.name()"></span></pre>
31974 * </div>
31975 * </file>
31976 * <file name="app.js">
31977 * angular.module('getterSetterExample', [])
31978 * .controller('ExampleController', ['$scope', function($scope) {
31979 * var _name = 'Brian';
31980 * $scope.user = {
31981 * name: function(newName) {
31982 * return angular.isDefined(newName) ? (_name = newName) : _name;
31983 * }
31984 * };
31985 * }]);
31986 * </file>
31987 * </example>
31988 *
31989 *
31990 * ## Programmatically changing options
31991 *
31992 * The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
31993 * watched for changes. However, it is possible to override the options on a single
31994 * {@link ngModel.NgModelController} instance with
31995 * {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}.
31996 * See also the example for
31997 * {@link ngModelOptions#default-events-extra-triggers-and-catch-all-debounce-values
31998 * Default events, extra triggers, and catch-all debounce values}.
31999 *
32000 *
32001 * ## Specifying timezones
32002 *
32003 * You can specify the timezone that date/time input directives expect by providing its name in the
32004 * `timezone` property.
32005 *
32006 *
32007 * ## Formatting the value of `time` and `datetime-local`
32008 *
32009 * With the options `timeSecondsFormat` and `timeStripZeroSeconds` it is possible to adjust the value
32010 * that is displayed in the control. Note that browsers may apply their own formatting
32011 * in the user interface.
32012 *
32013 <example name="ngModelOptions-time-format" module="timeExample">
32014 <file name="index.html">
32015 <time-example></time-example>
32016 </file>
32017 <file name="script.js">
32018 angular.module('timeExample', [])
32019 .component('timeExample', {
32020 templateUrl: 'timeExample.html',
32021 controller: function() {
32022 this.time = new Date(1970, 0, 1, 14, 57, 0);
32023
32024 this.options = {
32025 timeSecondsFormat: 'ss',
32026 timeStripZeroSeconds: true
32027 };
32028
32029 this.optionChange = function() {
32030 this.timeForm.timeFormatted.$overrideModelOptions(this.options);
32031 this.time = new Date(this.time);
32032 };
32033 }
32034 });
32035 </file>
32036 <file name="timeExample.html">
32037 <form name="$ctrl.timeForm">
32038 <strong>Default</strong>:
32039 <input type="time" ng-model="$ctrl.time" step="any" /><br>
32040 <strong>With options</strong>:
32041 <input type="time" name="timeFormatted" ng-model="$ctrl.time" step="any" ng-model-options="$ctrl.options" />
32042 <br>
32043
32044 Options:<br>
32045 <code>timeSecondsFormat</code>:
32046 <input
32047 type="text"
32048 ng-model="$ctrl.options.timeSecondsFormat"
32049 ng-change="$ctrl.optionChange()">
32050 <br>
32051 <code>timeStripZeroSeconds</code>:
32052 <input
32053 type="checkbox"
32054 ng-model="$ctrl.options.timeStripZeroSeconds"
32055 ng-change="$ctrl.optionChange()">
32056 </form>
32057 </file>
32058 * </example>
32059 *
32060 * @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
32061 * and its descendents.
32062 *
32063 * **General options**:
32064 *
32065 * - `updateOn`: string specifying which event should the input be bound to. You can set several
32066 * events using an space delimited list. There is a special event called `default` that
32067 * matches the default events belonging to the control. These are the events that are bound to
32068 * the control, and when fired, update the `$viewValue` via `$setViewValue`.
32069 *
32070 * `ngModelOptions` considers every event that is not listed in `updateOn` a "default" event,
32071 * since different control types use different default events.
32072 *
32073 * See also the section {@link ngModelOptions#triggering-and-debouncing-model-updates
32074 * Triggering and debouncing model updates}.
32075 *
32076 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
32077 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
32078 * custom value for each event. For example:
32079 * ```
32080 * ng-model-options="{
32081 * updateOn: 'default blur',
32082 * debounce: { 'default': 500, 'blur': 0 }
32083 * }"
32084 * ```
32085 * You can use the `*` key to specify a debounce value that applies to all events that are not
32086 * specifically listed. In the following example, `mouseup` would have a debounce delay of 1000:
32087 * ```
32088 * ng-model-options="{
32089 * updateOn: 'default blur mouseup',
32090 * debounce: { 'default': 500, 'blur': 0, '*': 1000 }
32091 * }"
32092 * ```
32093 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
32094 * not validate correctly instead of the default behavior of setting the model to undefined.
32095 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
32096 * `ngModel` as getters/setters.
32097 *
32098 *
32099 * **Input-type specific options**:
32100 *
32101 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
32102 * `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the
32103 * continental US time zone abbreviations, but for general use, use a time zone offset, for
32104 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
32105 * If not specified, the timezone of the browser will be used.
32106 * Note that changing the timezone will have no effect on the current date, and is only applied after
32107 * the next input / model change.
32108 *
32109 * - `timeSecondsFormat`: Defines if the `time` and `datetime-local` types should show seconds and
32110 * milliseconds. The option follows the format string of {@link date date filter}.
32111 * By default, the options is `undefined` which is equal to `'ss.sss'` (seconds and milliseconds).
32112 * The other options are `'ss'` (strips milliseconds), and `''` (empty string), which strips both
32113 * seconds and milliseconds.
32114 * Note that browsers that support `time` and `datetime-local` require the hour and minutes
32115 * part of the time string, and may show the value differently in the user interface.
32116 * {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
32117 *
32118 * - `timeStripZeroSeconds`: Defines if the `time` and `datetime-local` types should strip the
32119 * seconds and milliseconds from the formatted value if they are zero. This option is applied
32120 * after `timeSecondsFormat`.
32121 * This option can be used to make the formatting consistent over different browsers, as some
32122 * browsers with support for `time` will natively hide the milliseconds and
32123 * seconds if they are zero, but others won't, and browsers that don't implement these input
32124 * types will always show the full string.
32125 * {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
32126 *
32127 */
32128var ngModelOptionsDirective = function() {
32129 NgModelOptionsController.$inject = ['$attrs', '$scope'];
32130 function NgModelOptionsController($attrs, $scope) {
32131 this.$$attrs = $attrs;
32132 this.$$scope = $scope;
32133 }
32134 NgModelOptionsController.prototype = {
32135 $onInit: function() {
32136 var parentOptions = this.parentCtrl ? this.parentCtrl.$options : defaultModelOptions;
32137 var modelOptionsDefinition = this.$$scope.$eval(this.$$attrs.ngModelOptions);
32138
32139 this.$options = parentOptions.createChild(modelOptionsDefinition);
32140 }
32141 };
32142
32143 return {
32144 restrict: 'A',
32145 // ngModelOptions needs to run before ngModel and input directives
32146 priority: 10,
32147 require: {parentCtrl: '?^^ngModelOptions'},
32148 bindToController: true,
32149 controller: NgModelOptionsController
32150 };
32151};
32152
32153
32154// shallow copy over values from `src` that are not already specified on `dst`
32155function defaults(dst, src) {
32156 forEach(src, function(value, key) {
32157 if (!isDefined(dst[key])) {
32158 dst[key] = value;
32159 }
32160 });
32161}
32162
32163/**
32164 * @ngdoc directive
32165 * @name ngNonBindable
32166 * @restrict AC
32167 * @priority 1000
32168 * @element ANY
32169 *
32170 * @description
32171 * The `ngNonBindable` directive tells AngularJS not to compile or bind the contents of the current
32172 * DOM element, including directives on the element itself that have a lower priority than
32173 * `ngNonBindable`. This is useful if the element contains what appears to be AngularJS directives
32174 * and bindings but which should be ignored by AngularJS. This could be the case if you have a site
32175 * that displays snippets of code, for instance.
32176 *
32177 * @example
32178 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
32179 * but the one wrapped in `ngNonBindable` is left alone.
32180 *
32181 <example name="ng-non-bindable">
32182 <file name="index.html">
32183 <div>Normal: {{1 + 2}}</div>
32184 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
32185 </file>
32186 <file name="protractor.js" type="protractor">
32187 it('should check ng-non-bindable', function() {
32188 expect(element(by.binding('1 + 2')).getText()).toContain('3');
32189 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
32190 });
32191 </file>
32192 </example>
32193 */
32194var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
32195
32196/* exported ngOptionsDirective */
32197
32198/* global jqLiteRemove */
32199
32200var ngOptionsMinErr = minErr('ngOptions');
32201
32202/**
32203 * @ngdoc directive
32204 * @name ngOptions
32205 * @restrict A
32206 *
32207 * @description
32208 *
32209 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
32210 * elements for the `<select>` element using the array or object obtained by evaluating the
32211 * `ngOptions` comprehension expression.
32212 *
32213 * In many cases, {@link ng.directive:ngRepeat ngRepeat} can be used on `<option>` elements instead of
32214 * `ngOptions` to achieve a similar result. However, `ngOptions` provides some benefits:
32215 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
32216 * comprehension expression
32217 * - reduced memory consumption by not creating a new scope for each repeated instance
32218 * - increased render speed by creating the options in a documentFragment instead of individually
32219 *
32220 * When an item in the `<select>` menu is selected, the array element or object property
32221 * represented by the selected option will be bound to the model identified by the `ngModel`
32222 * directive.
32223 *
32224 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
32225 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
32226 * option. See example below for demonstration.
32227 *
32228 * ## Complex Models (objects or collections)
32229 *
32230 * By default, `ngModel` watches the model by reference, not value. This is important to know when
32231 * binding the select to a model that is an object or a collection.
32232 *
32233 * One issue occurs if you want to preselect an option. For example, if you set
32234 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
32235 * because the objects are not identical. So by default, you should always reference the item in your collection
32236 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
32237 *
32238 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
32239 * of the item not by reference, but by the result of the `track by` expression. For example, if your
32240 * collection items have an id property, you would `track by item.id`.
32241 *
32242 * A different issue with objects or collections is that ngModel won't detect if an object property or
32243 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
32244 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
32245 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
32246 * has not changed identity, but only a property on the object or an item in the collection changes.
32247 *
32248 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
32249 * if the model is an array). This means that changing a property deeper than the first level inside the
32250 * object/collection will not trigger a re-rendering.
32251 *
32252 * ## `select` **`as`**
32253 *
32254 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
32255 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
32256 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
32257 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
32258 *
32259 *
32260 * ### `select` **`as`** and **`track by`**
32261 *
32262 * <div class="alert alert-warning">
32263 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
32264 * </div>
32265 *
32266 * Given this array of items on the $scope:
32267 *
32268 * ```js
32269 * $scope.items = [{
32270 * id: 1,
32271 * label: 'aLabel',
32272 * subItem: { name: 'aSubItem' }
32273 * }, {
32274 * id: 2,
32275 * label: 'bLabel',
32276 * subItem: { name: 'bSubItem' }
32277 * }];
32278 * ```
32279 *
32280 * This will work:
32281 *
32282 * ```html
32283 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
32284 * ```
32285 * ```js
32286 * $scope.selected = $scope.items[0];
32287 * ```
32288 *
32289 * but this will not work:
32290 *
32291 * ```html
32292 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
32293 * ```
32294 * ```js
32295 * $scope.selected = $scope.items[0].subItem;
32296 * ```
32297 *
32298 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
32299 * `items` array. Because the selected option has been set programmatically in the controller, the
32300 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
32301 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
32302 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
32303 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
32304 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
32305 *
32306 *
32307 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
32308 * @param {comprehension_expression} ngOptions in one of the following forms:
32309 *
32310 * * for array data sources:
32311 * * `label` **`for`** `value` **`in`** `array`
32312 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
32313 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
32314 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
32315 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
32316 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
32317 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
32318 * (for including a filter with `track by`)
32319 * * for object data sources:
32320 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
32321 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
32322 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
32323 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
32324 * * `select` **`as`** `label` **`group by`** `group`
32325 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
32326 * * `select` **`as`** `label` **`disable when`** `disable`
32327 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
32328 *
32329 * Where:
32330 *
32331 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
32332 * * `value`: local variable which will refer to each item in the `array` or each property value
32333 * of `object` during iteration.
32334 * * `key`: local variable which will refer to a property name in `object` during iteration.
32335 * * `label`: The result of this expression will be the label for `<option>` element. The
32336 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
32337 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
32338 * element. If not specified, `select` expression will default to `value`.
32339 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
32340 * DOM element.
32341 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
32342 * element. Return `true` to disable.
32343 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
32344 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
32345 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
32346 * even when the options are recreated (e.g. reloaded from the server).
32347 * @param {string=} name Property name of the form under which the control is published.
32348 * @param {string=} required The control is considered valid only if value is entered.
32349 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
32350 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
32351 * `required` when you want to data-bind to the `required` attribute.
32352 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the
32353 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
32354 *
32355 * @example
32356 <example module="selectExample" name="select">
32357 <file name="index.html">
32358 <script>
32359 angular.module('selectExample', [])
32360 .controller('ExampleController', ['$scope', function($scope) {
32361 $scope.colors = [
32362 {name:'black', shade:'dark'},
32363 {name:'white', shade:'light', notAnOption: true},
32364 {name:'red', shade:'dark'},
32365 {name:'blue', shade:'dark', notAnOption: true},
32366 {name:'yellow', shade:'light', notAnOption: false}
32367 ];
32368 $scope.myColor = $scope.colors[2]; // red
32369 }]);
32370 </script>
32371 <div ng-controller="ExampleController">
32372 <ul>
32373 <li ng-repeat="color in colors">
32374 <label>Name: <input ng-model="color.name"></label>
32375 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
32376 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
32377 </li>
32378 <li>
32379 <button ng-click="colors.push({})">add</button>
32380 </li>
32381 </ul>
32382 <hr/>
32383 <label>Color (null not allowed):
32384 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
32385 </label><br/>
32386 <label>Color (null allowed):
32387 <span class="nullable">
32388 <select ng-model="myColor" ng-options="color.name for color in colors">
32389 <option value="">-- choose color --</option>
32390 </select>
32391 </span></label><br/>
32392
32393 <label>Color grouped by shade:
32394 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
32395 </select>
32396 </label><br/>
32397
32398 <label>Color grouped by shade, with some disabled:
32399 <select ng-model="myColor"
32400 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
32401 </select>
32402 </label><br/>
32403
32404
32405
32406 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
32407 <br/>
32408 <hr/>
32409 Currently selected: {{ {selected_color:myColor} }}
32410 <div style="border:solid 1px black; height:20px"
32411 ng-style="{'background-color':myColor.name}">
32412 </div>
32413 </div>
32414 </file>
32415 <file name="protractor.js" type="protractor">
32416 it('should check ng-options', function() {
32417 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
32418 element.all(by.model('myColor')).first().click();
32419 element.all(by.css('select[ng-model="myColor"] option')).first().click();
32420 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
32421 element(by.css('.nullable select[ng-model="myColor"]')).click();
32422 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
32423 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
32424 });
32425 </file>
32426 </example>
32427 */
32428
32429/* eslint-disable max-len */
32430// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555000000000666666666666600000007777777777777000000000000000888888888800000000000000000009999999999
32431var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([$\w][$\w]*)|(?:\(\s*([$\w][$\w]*)\s*,\s*([$\w][$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
32432 // 1: value expression (valueFn)
32433 // 2: label expression (displayFn)
32434 // 3: group by expression (groupByFn)
32435 // 4: disable when expression (disableWhenFn)
32436 // 5: array item variable name
32437 // 6: object item key variable name
32438 // 7: object item value variable name
32439 // 8: collection expression
32440 // 9: track by expression
32441/* eslint-enable */
32442
32443
32444var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) {
32445
32446 function parseOptionsExpression(optionsExp, selectElement, scope) {
32447
32448 var match = optionsExp.match(NG_OPTIONS_REGEXP);
32449 if (!(match)) {
32450 throw ngOptionsMinErr('iexp',
32451 'Expected expression in form of ' +
32452 '\'_select_ (as _label_)? for (_key_,)?_value_ in _collection_\'' +
32453 ' but got \'{0}\'. Element: {1}',
32454 optionsExp, startingTag(selectElement));
32455 }
32456
32457 // Extract the parts from the ngOptions expression
32458
32459 // The variable name for the value of the item in the collection
32460 var valueName = match[5] || match[7];
32461 // The variable name for the key of the item in the collection
32462 var keyName = match[6];
32463
32464 // An expression that generates the viewValue for an option if there is a label expression
32465 var selectAs = / as /.test(match[0]) && match[1];
32466 // An expression that is used to track the id of each object in the options collection
32467 var trackBy = match[9];
32468 // An expression that generates the viewValue for an option if there is no label expression
32469 var valueFn = $parse(match[2] ? match[1] : valueName);
32470 var selectAsFn = selectAs && $parse(selectAs);
32471 var viewValueFn = selectAsFn || valueFn;
32472 var trackByFn = trackBy && $parse(trackBy);
32473
32474 // Get the value by which we are going to track the option
32475 // if we have a trackFn then use that (passing scope and locals)
32476 // otherwise just hash the given viewValue
32477 var getTrackByValueFn = trackBy ?
32478 function(value, locals) { return trackByFn(scope, locals); } :
32479 function getHashOfValue(value) { return hashKey(value); };
32480 var getTrackByValue = function(value, key) {
32481 return getTrackByValueFn(value, getLocals(value, key));
32482 };
32483
32484 var displayFn = $parse(match[2] || match[1]);
32485 var groupByFn = $parse(match[3] || '');
32486 var disableWhenFn = $parse(match[4] || '');
32487 var valuesFn = $parse(match[8]);
32488
32489 var locals = {};
32490 var getLocals = keyName ? function(value, key) {
32491 locals[keyName] = key;
32492 locals[valueName] = value;
32493 return locals;
32494 } : function(value) {
32495 locals[valueName] = value;
32496 return locals;
32497 };
32498
32499
32500 function Option(selectValue, viewValue, label, group, disabled) {
32501 this.selectValue = selectValue;
32502 this.viewValue = viewValue;
32503 this.label = label;
32504 this.group = group;
32505 this.disabled = disabled;
32506 }
32507
32508 function getOptionValuesKeys(optionValues) {
32509 var optionValuesKeys;
32510
32511 if (!keyName && isArrayLike(optionValues)) {
32512 optionValuesKeys = optionValues;
32513 } else {
32514 // if object, extract keys, in enumeration order, unsorted
32515 optionValuesKeys = [];
32516 for (var itemKey in optionValues) {
32517 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
32518 optionValuesKeys.push(itemKey);
32519 }
32520 }
32521 }
32522 return optionValuesKeys;
32523 }
32524
32525 return {
32526 trackBy: trackBy,
32527 getTrackByValue: getTrackByValue,
32528 getWatchables: $parse(valuesFn, function(optionValues) {
32529 // Create a collection of things that we would like to watch (watchedArray)
32530 // so that they can all be watched using a single $watchCollection
32531 // that only runs the handler once if anything changes
32532 var watchedArray = [];
32533 optionValues = optionValues || [];
32534
32535 var optionValuesKeys = getOptionValuesKeys(optionValues);
32536 var optionValuesLength = optionValuesKeys.length;
32537 for (var index = 0; index < optionValuesLength; index++) {
32538 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
32539 var value = optionValues[key];
32540
32541 var locals = getLocals(value, key);
32542 var selectValue = getTrackByValueFn(value, locals);
32543 watchedArray.push(selectValue);
32544
32545 // Only need to watch the displayFn if there is a specific label expression
32546 if (match[2] || match[1]) {
32547 var label = displayFn(scope, locals);
32548 watchedArray.push(label);
32549 }
32550
32551 // Only need to watch the disableWhenFn if there is a specific disable expression
32552 if (match[4]) {
32553 var disableWhen = disableWhenFn(scope, locals);
32554 watchedArray.push(disableWhen);
32555 }
32556 }
32557 return watchedArray;
32558 }),
32559
32560 getOptions: function() {
32561
32562 var optionItems = [];
32563 var selectValueMap = {};
32564
32565 // The option values were already computed in the `getWatchables` fn,
32566 // which must have been called to trigger `getOptions`
32567 var optionValues = valuesFn(scope) || [];
32568 var optionValuesKeys = getOptionValuesKeys(optionValues);
32569 var optionValuesLength = optionValuesKeys.length;
32570
32571 for (var index = 0; index < optionValuesLength; index++) {
32572 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
32573 var value = optionValues[key];
32574 var locals = getLocals(value, key);
32575 var viewValue = viewValueFn(scope, locals);
32576 var selectValue = getTrackByValueFn(viewValue, locals);
32577 var label = displayFn(scope, locals);
32578 var group = groupByFn(scope, locals);
32579 var disabled = disableWhenFn(scope, locals);
32580 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
32581
32582 optionItems.push(optionItem);
32583 selectValueMap[selectValue] = optionItem;
32584 }
32585
32586 return {
32587 items: optionItems,
32588 selectValueMap: selectValueMap,
32589 getOptionFromViewValue: function(value) {
32590 return selectValueMap[getTrackByValue(value)];
32591 },
32592 getViewValueFromOption: function(option) {
32593 // If the viewValue could be an object that may be mutated by the application,
32594 // we need to make a copy and not return the reference to the value on the option.
32595 return trackBy ? copy(option.viewValue) : option.viewValue;
32596 }
32597 };
32598 }
32599 };
32600 }
32601
32602
32603 // Support: IE 9 only
32604 // We can't just jqLite('<option>') since jqLite is not smart enough
32605 // to create it in <select> and IE barfs otherwise.
32606 var optionTemplate = window.document.createElement('option'),
32607 optGroupTemplate = window.document.createElement('optgroup');
32608
32609 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
32610
32611 var selectCtrl = ctrls[0];
32612 var ngModelCtrl = ctrls[1];
32613 var multiple = attr.multiple;
32614
32615 // The emptyOption allows the application developer to provide their own custom "empty"
32616 // option when the viewValue does not match any of the option values.
32617 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
32618 if (children[i].value === '') {
32619 selectCtrl.hasEmptyOption = true;
32620 selectCtrl.emptyOption = children.eq(i);
32621 break;
32622 }
32623 }
32624
32625 // The empty option will be compiled and rendered before we first generate the options
32626 selectElement.empty();
32627
32628 var providedEmptyOption = !!selectCtrl.emptyOption;
32629
32630 var unknownOption = jqLite(optionTemplate.cloneNode(false));
32631 unknownOption.val('?');
32632
32633 var options;
32634 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
32635 // This stores the newly created options before they are appended to the select.
32636 // Since the contents are removed from the fragment when it is appended,
32637 // we only need to create it once.
32638 var listFragment = $document[0].createDocumentFragment();
32639
32640 // Overwrite the implementation. ngOptions doesn't use hashes
32641 selectCtrl.generateUnknownOptionValue = function(val) {
32642 return '?';
32643 };
32644
32645 // Update the controller methods for multiple selectable options
32646 if (!multiple) {
32647
32648 selectCtrl.writeValue = function writeNgOptionsValue(value) {
32649 // The options might not be defined yet when ngModel tries to render
32650 if (!options) return;
32651
32652 var selectedOption = selectElement[0].options[selectElement[0].selectedIndex];
32653 var option = options.getOptionFromViewValue(value);
32654
32655 // Make sure to remove the selected attribute from the previously selected option
32656 // Otherwise, screen readers might get confused
32657 if (selectedOption) selectedOption.removeAttribute('selected');
32658
32659 if (option) {
32660 // Don't update the option when it is already selected.
32661 // For example, the browser will select the first option by default. In that case,
32662 // most properties are set automatically - except the `selected` attribute, which we
32663 // set always
32664
32665 if (selectElement[0].value !== option.selectValue) {
32666 selectCtrl.removeUnknownOption();
32667
32668 selectElement[0].value = option.selectValue;
32669 option.element.selected = true;
32670 }
32671
32672 option.element.setAttribute('selected', 'selected');
32673 } else {
32674 selectCtrl.selectUnknownOrEmptyOption(value);
32675 }
32676 };
32677
32678 selectCtrl.readValue = function readNgOptionsValue() {
32679
32680 var selectedOption = options.selectValueMap[selectElement.val()];
32681
32682 if (selectedOption && !selectedOption.disabled) {
32683 selectCtrl.unselectEmptyOption();
32684 selectCtrl.removeUnknownOption();
32685 return options.getViewValueFromOption(selectedOption);
32686 }
32687 return null;
32688 };
32689
32690 // If we are using `track by` then we must watch the tracked value on the model
32691 // since ngModel only watches for object identity change
32692 // FIXME: When a user selects an option, this watch will fire needlessly
32693 if (ngOptions.trackBy) {
32694 scope.$watch(
32695 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
32696 function() { ngModelCtrl.$render(); }
32697 );
32698 }
32699
32700 } else {
32701
32702 selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
32703 // The options might not be defined yet when ngModel tries to render
32704 if (!options) return;
32705
32706 // Only set `<option>.selected` if necessary, in order to prevent some browsers from
32707 // scrolling to `<option>` elements that are outside the `<select>` element's viewport.
32708 var selectedOptions = values && values.map(getAndUpdateSelectedOption) || [];
32709
32710 options.items.forEach(function(option) {
32711 if (option.element.selected && !includes(selectedOptions, option)) {
32712 option.element.selected = false;
32713 }
32714 });
32715 };
32716
32717
32718 selectCtrl.readValue = function readNgOptionsMultiple() {
32719 var selectedValues = selectElement.val() || [],
32720 selections = [];
32721
32722 forEach(selectedValues, function(value) {
32723 var option = options.selectValueMap[value];
32724 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
32725 });
32726
32727 return selections;
32728 };
32729
32730 // If we are using `track by` then we must watch these tracked values on the model
32731 // since ngModel only watches for object identity change
32732 if (ngOptions.trackBy) {
32733
32734 scope.$watchCollection(function() {
32735 if (isArray(ngModelCtrl.$viewValue)) {
32736 return ngModelCtrl.$viewValue.map(function(value) {
32737 return ngOptions.getTrackByValue(value);
32738 });
32739 }
32740 }, function() {
32741 ngModelCtrl.$render();
32742 });
32743
32744 }
32745 }
32746
32747 if (providedEmptyOption) {
32748
32749 // compile the element since there might be bindings in it
32750 $compile(selectCtrl.emptyOption)(scope);
32751
32752 selectElement.prepend(selectCtrl.emptyOption);
32753
32754 if (selectCtrl.emptyOption[0].nodeType === NODE_TYPE_COMMENT) {
32755 // This means the empty option has currently no actual DOM node, probably because
32756 // it has been modified by a transclusion directive.
32757 selectCtrl.hasEmptyOption = false;
32758
32759 // Redefine the registerOption function, which will catch
32760 // options that are added by ngIf etc. (rendering of the node is async because of
32761 // lazy transclusion)
32762 selectCtrl.registerOption = function(optionScope, optionEl) {
32763 if (optionEl.val() === '') {
32764 selectCtrl.hasEmptyOption = true;
32765 selectCtrl.emptyOption = optionEl;
32766 selectCtrl.emptyOption.removeClass('ng-scope');
32767 // This ensures the new empty option is selected if previously no option was selected
32768 ngModelCtrl.$render();
32769
32770 optionEl.on('$destroy', function() {
32771 var needsRerender = selectCtrl.$isEmptyOptionSelected();
32772
32773 selectCtrl.hasEmptyOption = false;
32774 selectCtrl.emptyOption = undefined;
32775
32776 if (needsRerender) ngModelCtrl.$render();
32777 });
32778 }
32779 };
32780
32781 } else {
32782 // remove the class, which is added automatically because we recompile the element and it
32783 // becomes the compilation root
32784 selectCtrl.emptyOption.removeClass('ng-scope');
32785 }
32786
32787 }
32788
32789 // We will re-render the option elements if the option values or labels change
32790 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
32791
32792 // ------------------------------------------------------------------ //
32793
32794 function addOptionElement(option, parent) {
32795 var optionElement = optionTemplate.cloneNode(false);
32796 parent.appendChild(optionElement);
32797 updateOptionElement(option, optionElement);
32798 }
32799
32800 function getAndUpdateSelectedOption(viewValue) {
32801 var option = options.getOptionFromViewValue(viewValue);
32802 var element = option && option.element;
32803
32804 if (element && !element.selected) element.selected = true;
32805
32806 return option;
32807 }
32808
32809 function updateOptionElement(option, element) {
32810 option.element = element;
32811 element.disabled = option.disabled;
32812 // Support: IE 11 only, Edge 12-13 only
32813 // NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive
32814 // selects in certain circumstances when multiple selects are next to each other and display
32815 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
32816 // See https://github.com/angular/angular.js/issues/11314 for more info.
32817 // This is unfortunately untestable with unit / e2e tests
32818 if (option.label !== element.label) {
32819 element.label = option.label;
32820 element.textContent = option.label;
32821 }
32822 element.value = option.selectValue;
32823 }
32824
32825 function updateOptions() {
32826 var previousValue = options && selectCtrl.readValue();
32827
32828 // We must remove all current options, but cannot simply set innerHTML = null
32829 // since the providedEmptyOption might have an ngIf on it that inserts comments which we
32830 // must preserve.
32831 // Instead, iterate over the current option elements and remove them or their optgroup
32832 // parents
32833 if (options) {
32834
32835 for (var i = options.items.length - 1; i >= 0; i--) {
32836 var option = options.items[i];
32837 if (isDefined(option.group)) {
32838 jqLiteRemove(option.element.parentNode);
32839 } else {
32840 jqLiteRemove(option.element);
32841 }
32842 }
32843 }
32844
32845 options = ngOptions.getOptions();
32846
32847 var groupElementMap = {};
32848
32849 options.items.forEach(function addOption(option) {
32850 var groupElement;
32851
32852 if (isDefined(option.group)) {
32853
32854 // This option is to live in a group
32855 // See if we have already created this group
32856 groupElement = groupElementMap[option.group];
32857
32858 if (!groupElement) {
32859
32860 groupElement = optGroupTemplate.cloneNode(false);
32861 listFragment.appendChild(groupElement);
32862
32863 // Update the label on the group element
32864 // "null" is special cased because of Safari
32865 groupElement.label = option.group === null ? 'null' : option.group;
32866
32867 // Store it for use later
32868 groupElementMap[option.group] = groupElement;
32869 }
32870
32871 addOptionElement(option, groupElement);
32872
32873 } else {
32874
32875 // This option is not in a group
32876 addOptionElement(option, listFragment);
32877 }
32878 });
32879
32880 selectElement[0].appendChild(listFragment);
32881
32882 ngModelCtrl.$render();
32883
32884 // Check to see if the value has changed due to the update to the options
32885 if (!ngModelCtrl.$isEmpty(previousValue)) {
32886 var nextValue = selectCtrl.readValue();
32887 var isNotPrimitive = ngOptions.trackBy || multiple;
32888 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
32889 ngModelCtrl.$setViewValue(nextValue);
32890 ngModelCtrl.$render();
32891 }
32892 }
32893 }
32894 }
32895
32896 return {
32897 restrict: 'A',
32898 terminal: true,
32899 require: ['select', 'ngModel'],
32900 link: {
32901 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
32902 // Deactivate the SelectController.register method to prevent
32903 // option directives from accidentally registering themselves
32904 // (and unwanted $destroy handlers etc.)
32905 ctrls[0].registerOption = noop;
32906 },
32907 post: ngOptionsPostLink
32908 }
32909 };
32910}];
32911
32912/**
32913 * @ngdoc directive
32914 * @name ngPluralize
32915 * @restrict EA
32916 *
32917 * @description
32918 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
32919 * These rules are bundled with angular.js, but can be overridden
32920 * (see {@link guide/i18n AngularJS i18n} dev guide). You configure ngPluralize directive
32921 * by specifying the mappings between
32922 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
32923 * and the strings to be displayed.
32924 *
32925 * ## Plural categories and explicit number rules
32926 * There are two
32927 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
32928 * in AngularJS's default en-US locale: "one" and "other".
32929 *
32930 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
32931 * any number that is not 1), an explicit number rule can only match one number. For example, the
32932 * explicit number rule for "3" matches the number 3. There are examples of plural categories
32933 * and explicit number rules throughout the rest of this documentation.
32934 *
32935 * ## Configuring ngPluralize
32936 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
32937 * You can also provide an optional attribute, `offset`.
32938 *
32939 * The value of the `count` attribute can be either a string or an {@link guide/expression
32940 * AngularJS expression}; these are evaluated on the current scope for its bound value.
32941 *
32942 * The `when` attribute specifies the mappings between plural categories and the actual
32943 * string to be displayed. The value of the attribute should be a JSON object.
32944 *
32945 * The following example shows how to configure ngPluralize:
32946 *
32947 * ```html
32948 * <ng-pluralize count="personCount"
32949 when="{'0': 'Nobody is viewing.',
32950 * 'one': '1 person is viewing.',
32951 * 'other': '{} people are viewing.'}">
32952 * </ng-pluralize>
32953 *```
32954 *
32955 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
32956 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
32957 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
32958 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
32959 * show "a dozen people are viewing".
32960 *
32961 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
32962 * into pluralized strings. In the previous example, AngularJS will replace `{}` with
32963 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
32964 * for <span ng-non-bindable>{{numberExpression}}</span>.
32965 *
32966 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
32967 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
32968 *
32969 * ## Configuring ngPluralize with offset
32970 * The `offset` attribute allows further customization of pluralized text, which can result in
32971 * a better user experience. For example, instead of the message "4 people are viewing this document",
32972 * you might display "John, Kate and 2 others are viewing this document".
32973 * The offset attribute allows you to offset a number by any desired value.
32974 * Let's take a look at an example:
32975 *
32976 * ```html
32977 * <ng-pluralize count="personCount" offset=2
32978 * when="{'0': 'Nobody is viewing.',
32979 * '1': '{{person1}} is viewing.',
32980 * '2': '{{person1}} and {{person2}} are viewing.',
32981 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
32982 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
32983 * </ng-pluralize>
32984 * ```
32985 *
32986 * Notice that we are still using two plural categories(one, other), but we added
32987 * three explicit number rules 0, 1 and 2.
32988 * When one person, perhaps John, views the document, "John is viewing" will be shown.
32989 * When three people view the document, no explicit number rule is found, so
32990 * an offset of 2 is taken off 3, and AngularJS uses 1 to decide the plural category.
32991 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
32992 * is shown.
32993 *
32994 * Note that when you specify offsets, you must provide explicit number rules for
32995 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
32996 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
32997 * plural categories "one" and "other".
32998 *
32999 * @param {string|expression} count The variable to be bound to.
33000 * @param {string} when The mapping between plural category to its corresponding strings.
33001 * @param {number=} offset Offset to deduct from the total number.
33002 *
33003 * @example
33004 <example module="pluralizeExample" name="ng-pluralize">
33005 <file name="index.html">
33006 <script>
33007 angular.module('pluralizeExample', [])
33008 .controller('ExampleController', ['$scope', function($scope) {
33009 $scope.person1 = 'Igor';
33010 $scope.person2 = 'Misko';
33011 $scope.personCount = 1;
33012 }]);
33013 </script>
33014 <div ng-controller="ExampleController">
33015 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
33016 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
33017 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
33018
33019 <!--- Example with simple pluralization rules for en locale --->
33020 Without Offset:
33021 <ng-pluralize count="personCount"
33022 when="{'0': 'Nobody is viewing.',
33023 'one': '1 person is viewing.',
33024 'other': '{} people are viewing.'}">
33025 </ng-pluralize><br>
33026
33027 <!--- Example with offset --->
33028 With Offset(2):
33029 <ng-pluralize count="personCount" offset=2
33030 when="{'0': 'Nobody is viewing.',
33031 '1': '{{person1}} is viewing.',
33032 '2': '{{person1}} and {{person2}} are viewing.',
33033 'one': '{{person1}}, {{person2}} and one other person are viewing.',
33034 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
33035 </ng-pluralize>
33036 </div>
33037 </file>
33038 <file name="protractor.js" type="protractor">
33039 it('should show correct pluralized string', function() {
33040 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
33041 var withOffset = element.all(by.css('ng-pluralize')).get(1);
33042 var countInput = element(by.model('personCount'));
33043
33044 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
33045 expect(withOffset.getText()).toEqual('Igor is viewing.');
33046
33047 countInput.clear();
33048 countInput.sendKeys('0');
33049
33050 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
33051 expect(withOffset.getText()).toEqual('Nobody is viewing.');
33052
33053 countInput.clear();
33054 countInput.sendKeys('2');
33055
33056 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
33057 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
33058
33059 countInput.clear();
33060 countInput.sendKeys('3');
33061
33062 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
33063 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
33064
33065 countInput.clear();
33066 countInput.sendKeys('4');
33067
33068 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
33069 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
33070 });
33071 it('should show data-bound names', function() {
33072 var withOffset = element.all(by.css('ng-pluralize')).get(1);
33073 var personCount = element(by.model('personCount'));
33074 var person1 = element(by.model('person1'));
33075 var person2 = element(by.model('person2'));
33076 personCount.clear();
33077 personCount.sendKeys('4');
33078 person1.clear();
33079 person1.sendKeys('Di');
33080 person2.clear();
33081 person2.sendKeys('Vojta');
33082 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
33083 });
33084 </file>
33085 </example>
33086 */
33087var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
33088 var BRACE = /{}/g,
33089 IS_WHEN = /^when(Minus)?(.+)$/;
33090
33091 return {
33092 link: function(scope, element, attr) {
33093 var numberExp = attr.count,
33094 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
33095 offset = attr.offset || 0,
33096 whens = scope.$eval(whenExp) || {},
33097 whensExpFns = {},
33098 startSymbol = $interpolate.startSymbol(),
33099 endSymbol = $interpolate.endSymbol(),
33100 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
33101 watchRemover = angular.noop,
33102 lastCount;
33103
33104 forEach(attr, function(expression, attributeName) {
33105 var tmpMatch = IS_WHEN.exec(attributeName);
33106 if (tmpMatch) {
33107 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
33108 whens[whenKey] = element.attr(attr.$attr[attributeName]);
33109 }
33110 });
33111 forEach(whens, function(expression, key) {
33112 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
33113
33114 });
33115
33116 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
33117 var count = parseFloat(newVal);
33118 var countIsNaN = isNumberNaN(count);
33119
33120 if (!countIsNaN && !(count in whens)) {
33121 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
33122 // Otherwise, check it against pluralization rules in $locale service.
33123 count = $locale.pluralCat(count - offset);
33124 }
33125
33126 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
33127 // In JS `NaN !== NaN`, so we have to explicitly check.
33128 if ((count !== lastCount) && !(countIsNaN && isNumberNaN(lastCount))) {
33129 watchRemover();
33130 var whenExpFn = whensExpFns[count];
33131 if (isUndefined(whenExpFn)) {
33132 if (newVal != null) {
33133 $log.debug('ngPluralize: no rule defined for \'' + count + '\' in ' + whenExp);
33134 }
33135 watchRemover = noop;
33136 updateElementText();
33137 } else {
33138 watchRemover = scope.$watch(whenExpFn, updateElementText);
33139 }
33140 lastCount = count;
33141 }
33142 });
33143
33144 function updateElementText(newText) {
33145 element.text(newText || '');
33146 }
33147 }
33148 };
33149}];
33150
33151/**
33152 * @ngdoc directive
33153 * @name ngRef
33154 * @restrict A
33155 *
33156 * @description
33157 * The `ngRef` attribute tells AngularJS to assign the controller of a component (or a directive)
33158 * to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM
33159 * element to the scope.
33160 *
33161 * If the element with `ngRef` is destroyed `null` is assigned to the property.
33162 *
33163 * Note that if you want to assign from a child into the parent scope, you must initialize the
33164 * target property on the parent scope, otherwise `ngRef` will assign on the child scope.
33165 * This commonly happens when assigning elements or components wrapped in {@link ngIf} or
33166 * {@link ngRepeat}. See the second example below.
33167 *
33168 *
33169 * @element ANY
33170 * @param {string} ngRef property name - A valid AngularJS expression identifier to which the
33171 * controller or jqlite-wrapped DOM element will be bound.
33172 * @param {string=} ngRefRead read value - The name of a directive (or component) on this element,
33173 * or the special string `$element`. If a name is provided, `ngRef` will
33174 * assign the matching controller. If `$element` is provided, the element
33175 * itself is assigned (even if a controller is available).
33176 *
33177 *
33178 * @example
33179 * ### Simple toggle
33180 * This example shows how the controller of the component toggle
33181 * is reused in the template through the scope to use its logic.
33182 * <example name="ng-ref-component" module="myApp">
33183 * <file name="index.html">
33184 * <my-toggle ng-ref="myToggle"></my-toggle>
33185 * <button ng-click="myToggle.toggle()">Toggle</button>
33186 * <div ng-show="myToggle.isOpen()">
33187 * You are using a component in the same template to show it.
33188 * </div>
33189 * </file>
33190 * <file name="index.js">
33191 * angular.module('myApp', [])
33192 * .component('myToggle', {
33193 * controller: function ToggleController() {
33194 * var opened = false;
33195 * this.isOpen = function() { return opened; };
33196 * this.toggle = function() { opened = !opened; };
33197 * }
33198 * });
33199 * </file>
33200 * <file name="protractor.js" type="protractor">
33201 * it('should publish the toggle into the scope', function() {
33202 * var toggle = element(by.buttonText('Toggle'));
33203 * expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false);
33204 * toggle.click();
33205 * expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true);
33206 * });
33207 * </file>
33208 * </example>
33209 *
33210 * @example
33211 * ### ngRef inside scopes
33212 * This example shows how `ngRef` works with child scopes. The `ngRepeat`-ed `myWrapper` components
33213 * are assigned to the scope of `myRoot`, because the `toggles` property has been initialized.
33214 * The repeated `myToggle` components are published to the child scopes created by `ngRepeat`.
33215 * `ngIf` behaves similarly - the assignment of `myToggle` happens in the `ngIf` child scope,
33216 * because the target property has not been initialized on the `myRoot` component controller.
33217 *
33218 * <example name="ng-ref-scopes" module="myApp">
33219 * <file name="index.html">
33220 * <my-root></my-root>
33221 * </file>
33222 * <file name="index.js">
33223 * angular.module('myApp', [])
33224 * .component('myRoot', {
33225 * templateUrl: 'root.html',
33226 * controller: function() {
33227 * this.wrappers = []; // initialize the array so that the wrappers are assigned into the parent scope
33228 * }
33229 * })
33230 * .component('myToggle', {
33231 * template: '<strong>myToggle</strong><button ng-click="$ctrl.toggle()" ng-transclude></button>',
33232 * transclude: true,
33233 * controller: function ToggleController() {
33234 * var opened = false;
33235 * this.isOpen = function() { return opened; };
33236 * this.toggle = function() { opened = !opened; };
33237 * }
33238 * })
33239 * .component('myWrapper', {
33240 * transclude: true,
33241 * template: '<strong>myWrapper</strong>' +
33242 * '<div>ngRepeatToggle.isOpen(): {{$ctrl.ngRepeatToggle.isOpen() | json}}</div>' +
33243 * '<my-toggle ng-ref="$ctrl.ngRepeatToggle"><ng-transclude></ng-transclude></my-toggle>'
33244 * });
33245 * </file>
33246 * <file name="root.html">
33247 * <strong>myRoot</strong>
33248 * <my-toggle ng-ref="$ctrl.outerToggle">Outer Toggle</my-toggle>
33249 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
33250 * <div><em>wrappers assigned to root</em><br>
33251 * <div ng-repeat="wrapper in $ctrl.wrappers">
33252 * wrapper.ngRepeatToggle.isOpen(): {{wrapper.ngRepeatToggle.isOpen() | json}}
33253 * </div>
33254 *
33255 * <ul>
33256 * <li ng-repeat="(index, value) in [1,2,3]">
33257 * <strong>ngRepeat</strong>
33258 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
33259 * <my-wrapper ng-ref="$ctrl.wrappers[index]">ngRepeat Toggle {{$index + 1}}</my-wrapper>
33260 * </li>
33261 * </ul>
33262 *
33263 * <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen()}} // This is always undefined because it's
33264 * assigned to the child scope created by ngIf.
33265 * </div>
33266 * <div ng-if="true">
33267 <strong>ngIf</strong>
33268 * <my-toggle ng-ref="ngIfToggle">ngIf Toggle</my-toggle>
33269 * <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen() | json}}</div>
33270 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
33271 * </div>
33272 * </file>
33273 * <file name="styles.css">
33274 * ul {
33275 * list-style: none;
33276 * padding-left: 0;
33277 * }
33278 *
33279 * li[ng-repeat] {
33280 * background: lightgreen;
33281 * padding: 8px;
33282 * margin: 8px;
33283 * }
33284 *
33285 * [ng-if] {
33286 * background: lightgrey;
33287 * padding: 8px;
33288 * }
33289 *
33290 * my-root {
33291 * background: lightgoldenrodyellow;
33292 * padding: 8px;
33293 * display: block;
33294 * }
33295 *
33296 * my-wrapper {
33297 * background: lightsalmon;
33298 * padding: 8px;
33299 * display: block;
33300 * }
33301 *
33302 * my-toggle {
33303 * background: lightblue;
33304 * padding: 8px;
33305 * display: block;
33306 * }
33307 * </file>
33308 * <file name="protractor.js" type="protractor">
33309 * var OuterToggle = function() {
33310 * this.toggle = function() {
33311 * element(by.buttonText('Outer Toggle')).click();
33312 * };
33313 * this.isOpen = function() {
33314 * return element.all(by.binding('outerToggle.isOpen()')).first().getText();
33315 * };
33316 * };
33317 * var NgRepeatToggle = function(i) {
33318 * var parent = element.all(by.repeater('(index, value) in [1,2,3]')).get(i - 1);
33319 * this.toggle = function() {
33320 * element(by.buttonText('ngRepeat Toggle ' + i)).click();
33321 * };
33322 * this.isOpen = function() {
33323 * return parent.element(by.binding('ngRepeatToggle.isOpen() | json')).getText();
33324 * };
33325 * this.isOuterOpen = function() {
33326 * return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
33327 * };
33328 * };
33329 * var NgRepeatToggles = function() {
33330 * var toggles = [1,2,3].map(function(i) { return new NgRepeatToggle(i); });
33331 * this.forEach = function(fn) {
33332 * toggles.forEach(fn);
33333 * };
33334 * this.isOuterOpen = function(i) {
33335 * return toggles[i - 1].isOuterOpen();
33336 * };
33337 * };
33338 * var NgIfToggle = function() {
33339 * var parent = element(by.css('[ng-if]'));
33340 * this.toggle = function() {
33341 * element(by.buttonText('ngIf Toggle')).click();
33342 * };
33343 * this.isOpen = function() {
33344 * return by.binding('ngIfToggle.isOpen() | json').getText();
33345 * };
33346 * this.isOuterOpen = function() {
33347 * return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
33348 * };
33349 * };
33350 *
33351 * it('should toggle the outer toggle', function() {
33352 * var outerToggle = new OuterToggle();
33353 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
33354 * outerToggle.toggle();
33355 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
33356 * });
33357 *
33358 * it('should toggle all outer toggles', function() {
33359 * var outerToggle = new OuterToggle();
33360 * var repeatToggles = new NgRepeatToggles();
33361 * var ifToggle = new NgIfToggle();
33362 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
33363 * expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): false');
33364 * expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): false');
33365 * expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): false');
33366 * expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
33367 * outerToggle.toggle();
33368 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
33369 * expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): true');
33370 * expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): true');
33371 * expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): true');
33372 * expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): true');
33373 * });
33374 *
33375 * it('should toggle each repeat iteration separately', function() {
33376 * var repeatToggles = new NgRepeatToggles();
33377 *
33378 * repeatToggles.forEach(function(repeatToggle) {
33379 * expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): false');
33380 * expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
33381 * repeatToggle.toggle();
33382 * expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): true');
33383 * expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
33384 * });
33385 * });
33386 * </file>
33387 * </example>
33388 *
33389 */
33390
33391var ngRefMinErr = minErr('ngRef');
33392
33393var ngRefDirective = ['$parse', function($parse) {
33394 return {
33395 priority: -1, // Needed for compatibility with element transclusion on the same element
33396 restrict: 'A',
33397 compile: function(tElement, tAttrs) {
33398 // Get the expected controller name, converts <data-some-thing> into "someThing"
33399 var controllerName = directiveNormalize(nodeName_(tElement));
33400
33401 // Get the expression for value binding
33402 var getter = $parse(tAttrs.ngRef);
33403 var setter = getter.assign || function() {
33404 throw ngRefMinErr('nonassign', 'Expression in ngRef="{0}" is non-assignable!', tAttrs.ngRef);
33405 };
33406
33407 return function(scope, element, attrs) {
33408 var refValue;
33409
33410 if (attrs.hasOwnProperty('ngRefRead')) {
33411 if (attrs.ngRefRead === '$element') {
33412 refValue = element;
33413 } else {
33414 refValue = element.data('$' + attrs.ngRefRead + 'Controller');
33415
33416 if (!refValue) {
33417 throw ngRefMinErr(
33418 'noctrl',
33419 'The controller for ngRefRead="{0}" could not be found on ngRef="{1}"',
33420 attrs.ngRefRead,
33421 tAttrs.ngRef
33422 );
33423 }
33424 }
33425 } else {
33426 refValue = element.data('$' + controllerName + 'Controller');
33427 }
33428
33429 refValue = refValue || element;
33430
33431 setter(scope, refValue);
33432
33433 // when the element is removed, remove it (nullify it)
33434 element.on('$destroy', function() {
33435 // only remove it if value has not changed,
33436 // because animations (and other procedures) may duplicate elements
33437 if (getter(scope) === refValue) {
33438 setter(scope, null);
33439 }
33440 });
33441 };
33442 }
33443 };
33444}];
33445
33446/* exported ngRepeatDirective */
33447
33448/**
33449 * @ngdoc directive
33450 * @name ngRepeat
33451 * @multiElement
33452 * @restrict A
33453 *
33454 * @description
33455 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
33456 * instance gets its own scope, where the given loop variable is set to the current collection item,
33457 * and `$index` is set to the item index or key.
33458 *
33459 * Special properties are exposed on the local scope of each template instance, including:
33460 *
33461 * | Variable | Type | Details |
33462 * |-----------|-----------------|-----------------------------------------------------------------------------|
33463 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
33464 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
33465 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
33466 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
33467 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
33468 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
33469 *
33470 * <div class="alert alert-info">
33471 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
33472 * This may be useful when, for instance, nesting ngRepeats.
33473 * </div>
33474 *
33475 *
33476 * ## Iterating over object properties
33477 *
33478 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
33479 * syntax:
33480 *
33481 * ```js
33482 * <div ng-repeat="(key, value) in myObj"> ... </div>
33483 * ```
33484 *
33485 * However, there are a few limitations compared to array iteration:
33486 *
33487 * - The JavaScript specification does not define the order of keys
33488 * returned for an object, so AngularJS relies on the order returned by the browser
33489 * when running `for key in myObj`. Browsers generally follow the strategy of providing
33490 * keys in the order in which they were defined, although there are exceptions when keys are deleted
33491 * and reinstated. See the
33492 * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
33493 *
33494 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because
33495 * it's a prefix used by AngularJS for public (`$`) and private (`$$`) properties.
33496 *
33497 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with
33498 * objects, and will throw an error if used with one.
33499 *
33500 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array
33501 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
33502 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
33503 * or implement a `$watch` on the object yourself.
33504 *
33505 *
33506 * ## Tracking and Duplicates
33507 *
33508 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
33509 * the collection. When a change happens, `ngRepeat` then makes the corresponding changes to the DOM:
33510 *
33511 * * When an item is added, a new instance of the template is added to the DOM.
33512 * * When an item is removed, its template instance is removed from the DOM.
33513 * * When items are reordered, their respective templates are reordered in the DOM.
33514 *
33515 * To minimize creation of DOM elements, `ngRepeat` uses a function
33516 * to "keep track" of all items in the collection and their corresponding DOM elements.
33517 * For example, if an item is added to the collection, `ngRepeat` will know that all other items
33518 * already have DOM elements, and will not re-render them.
33519 *
33520 * All different types of tracking functions, their syntax, and their support for duplicate
33521 * items in collections can be found in the
33522 * {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}.
33523 *
33524 * <div class="alert alert-success">
33525 * **Best Practice:** If you are working with objects that have a unique identifier property, you
33526 * should track by this identifier instead of the object instance,
33527 * e.g. `item in items track by item.id`.
33528 * Should you reload your data later, `ngRepeat` will not have to rebuild the DOM elements for items
33529 * it has already rendered, even if the JavaScript objects in the collection have been substituted
33530 * for new ones. For large collections, this significantly improves rendering performance.
33531 * </div>
33532 *
33533 * ### Effects of DOM Element re-use
33534 *
33535 * When DOM elements are re-used, ngRepeat updates the scope for the element, which will
33536 * automatically update any active bindings on the template. However, other
33537 * functionality will not be updated, because the element is not re-created:
33538 *
33539 * - Directives are not re-compiled
33540 * - {@link guide/expression#one-time-binding one-time expressions} on the repeated template are not
33541 * updated if they have stabilized.
33542 *
33543 * The above affects all kinds of element re-use due to tracking, but may be especially visible
33544 * when tracking by `$index` due to the way ngRepeat re-uses elements.
33545 *
33546 * The following example shows the effects of different actions with tracking:
33547
33548 <example module="ngRepeat" name="ngRepeat-tracking" deps="angular-animate.js" animations="true">
33549 <file name="script.js">
33550 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) {
33551 var friends = [
33552 {name:'John', age:25},
33553 {name:'Mary', age:40},
33554 {name:'Peter', age:85}
33555 ];
33556
33557 $scope.removeFirst = function() {
33558 $scope.friends.shift();
33559 };
33560
33561 $scope.updateAge = function() {
33562 $scope.friends.forEach(function(el) {
33563 el.age = el.age + 5;
33564 });
33565 };
33566
33567 $scope.copy = function() {
33568 $scope.friends = angular.copy($scope.friends);
33569 };
33570
33571 $scope.reset = function() {
33572 $scope.friends = angular.copy(friends);
33573 };
33574
33575 $scope.reset();
33576 });
33577 </file>
33578 <file name="index.html">
33579 <div ng-controller="repeatController">
33580 <ol>
33581 <li>When you click "Update Age", only the first list updates the age, because all others have
33582 a one-time binding on the age property. If you then click "Copy", the current friend list
33583 is copied, and now the second list updates the age, because the identity of the collection items
33584 has changed and the list must be re-rendered. The 3rd and 4th list stay the same, because all the
33585 items are already known according to their tracking functions.
33586 </li>
33587 <li>When you click "Remove First", the 4th list has the wrong age on both remaining items. This is
33588 due to tracking by $index: when the first collection item is removed, ngRepeat reuses the first
33589 DOM element for the new first collection item, and so on. Since the age property is one-time
33590 bound, the value remains from the collection item which was previously at this index.
33591 </li>
33592 </ol>
33593
33594 <button ng-click="removeFirst()">Remove First</button>
33595 <button ng-click="updateAge()">Update Age</button>
33596 <button ng-click="copy()">Copy</button>
33597 <br><button ng-click="reset()">Reset List</button>
33598 <br>
33599 <code>track by $id(friend)</code> (default):
33600 <ul class="example-animate-container">
33601 <li class="animate-repeat" ng-repeat="friend in friends">
33602 {{friend.name}} is {{friend.age}} years old.
33603 </li>
33604 </ul>
33605 <code>track by $id(friend)</code> (default), with age one-time binding:
33606 <ul class="example-animate-container">
33607 <li class="animate-repeat" ng-repeat="friend in friends">
33608 {{friend.name}} is {{::friend.age}} years old.
33609 </li>
33610 </ul>
33611 <code>track by friend.name</code>, with age one-time binding:
33612 <ul class="example-animate-container">
33613 <li class="animate-repeat" ng-repeat="friend in friends track by friend.name">
33614 {{friend.name}} is {{::friend.age}} years old.
33615 </li>
33616 </ul>
33617 <code>track by $index</code>, with age one-time binding:
33618 <ul class="example-animate-container">
33619 <li class="animate-repeat" ng-repeat="friend in friends track by $index">
33620 {{friend.name}} is {{::friend.age}} years old.
33621 </li>
33622 </ul>
33623 </div>
33624 </file>
33625 <file name="animations.css">
33626 .example-animate-container {
33627 background:white;
33628 border:1px solid black;
33629 list-style:none;
33630 margin:0;
33631 padding:0 10px;
33632 }
33633
33634 .animate-repeat {
33635 line-height:30px;
33636 list-style:none;
33637 box-sizing:border-box;
33638 }
33639
33640 .animate-repeat.ng-move,
33641 .animate-repeat.ng-enter,
33642 .animate-repeat.ng-leave {
33643 transition:all linear 0.5s;
33644 }
33645
33646 .animate-repeat.ng-leave.ng-leave-active,
33647 .animate-repeat.ng-move,
33648 .animate-repeat.ng-enter {
33649 opacity:0;
33650 max-height:0;
33651 }
33652
33653 .animate-repeat.ng-leave,
33654 .animate-repeat.ng-move.ng-move-active,
33655 .animate-repeat.ng-enter.ng-enter-active {
33656 opacity:1;
33657 max-height:30px;
33658 }
33659 </file>
33660 </example>
33661
33662 *
33663 * ## Special repeat start and end points
33664 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
33665 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
33666 * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on)
33667 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
33668 *
33669 * The example below makes use of this feature:
33670 * ```html
33671 * <header ng-repeat-start="item in items">
33672 * Header {{ item }}
33673 * </header>
33674 * <div class="body">
33675 * Body {{ item }}
33676 * </div>
33677 * <footer ng-repeat-end>
33678 * Footer {{ item }}
33679 * </footer>
33680 * ```
33681 *
33682 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
33683 * ```html
33684 * <header>
33685 * Header A
33686 * </header>
33687 * <div class="body">
33688 * Body A
33689 * </div>
33690 * <footer>
33691 * Footer A
33692 * </footer>
33693 * <header>
33694 * Header B
33695 * </header>
33696 * <div class="body">
33697 * Body B
33698 * </div>
33699 * <footer>
33700 * Footer B
33701 * </footer>
33702 * ```
33703 *
33704 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
33705 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
33706 *
33707 * @animations
33708 * | Animation | Occurs |
33709 * |----------------------------------|-------------------------------------|
33710 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter |
33711 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out |
33712 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered |
33713 *
33714 * See the example below for defining CSS animations with ngRepeat.
33715 *
33716 * @element ANY
33717 * @scope
33718 * @priority 1000
33719 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
33720 * formats are currently supported:
33721 *
33722 * * `variable in expression` – where variable is the user defined loop variable and `expression`
33723 * is a scope expression giving the collection to enumerate.
33724 *
33725 * For example: `album in artist.albums`.
33726 *
33727 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
33728 * and `expression` is the scope expression giving the collection to enumerate.
33729 *
33730 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
33731 *
33732 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
33733 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
33734 * is specified, ng-repeat associates elements by identity. It is an error to have
33735 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
33736 * mapped to the same DOM element, which is not possible.)
33737 *
33738 * *Default tracking: $id()*: `item in items` is equivalent to `item in items track by $id(item)`.
33739 * This implies that the DOM elements will be associated by item identity in the collection.
33740 *
33741 * The built-in `$id()` function can be used to assign a unique
33742 * `$$hashKey` property to each item in the collection. This property is then used as a key to associated DOM elements
33743 * with the corresponding item in the collection by identity. Moving the same object would move
33744 * the DOM element in the same way in the DOM.
33745 * Note that the default id function does not support duplicate primitive values (`number`, `string`),
33746 * but supports duplictae non-primitive values (`object`) that are *equal* in shape.
33747 *
33748 * *Custom Expression*: It is possible to use any AngularJS expression to compute the tracking
33749 * id, for example with a function, or using a property on the collection items.
33750 * `item in items track by item.id` is a typical pattern when the items have a unique identifier,
33751 * e.g. database id. In this case the object identity does not matter. Two objects are considered
33752 * equivalent as long as their `id` property is same.
33753 * Tracking by unique identifier is the most performant way and should be used whenever possible.
33754 *
33755 * *$index*: This special property tracks the collection items by their index, and
33756 * re-uses the DOM elements that match that index, e.g. `item in items track by $index`. This can
33757 * be used for a performance improvement if no unique identfier is available and the identity of
33758 * the collection items cannot be easily computed. It also allows duplicates.
33759 *
33760 * <div class="alert alert-warning">
33761 * <strong>Note:</strong> Re-using DOM elements can have unforeseen effects. Read the
33762 * {@link ngRepeat#tracking-and-duplicates section on tracking and duplicates} for
33763 * more info.
33764 * </div>
33765 *
33766 * <div class="alert alert-warning">
33767 * <strong>Note:</strong> the `track by` expression must come last - after any filters, and the alias expression:
33768 * `item in items | filter:searchText as results track by item.id`
33769 * </div>
33770 *
33771 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
33772 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
33773 * when a filter is active on the repeater, but the filtered result set is empty.
33774 *
33775 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
33776 * the items have been processed through the filter.
33777 *
33778 * Please note that `as [variable name]` is not an operator but rather a part of ngRepeat
33779 * micro-syntax so it can be used only after all filters (and not as operator, inside an expression).
33780 *
33781 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results track by item.id` .
33782 *
33783 * @example
33784 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed
33785 * results by name or by age. New (entering) and removed (leaving) items are animated.
33786 <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true">
33787 <file name="index.html">
33788 <div ng-controller="repeatController">
33789 I have {{friends.length}} friends. They are:
33790 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
33791 <ul class="example-animate-container">
33792 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results track by friend.name">
33793 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
33794 </li>
33795 <li class="animate-repeat" ng-if="results.length === 0">
33796 <strong>No results found...</strong>
33797 </li>
33798 </ul>
33799 </div>
33800 </file>
33801 <file name="script.js">
33802 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) {
33803 $scope.friends = [
33804 {name:'John', age:25, gender:'boy'},
33805 {name:'Jessie', age:30, gender:'girl'},
33806 {name:'Johanna', age:28, gender:'girl'},
33807 {name:'Joy', age:15, gender:'girl'},
33808 {name:'Mary', age:28, gender:'girl'},
33809 {name:'Peter', age:95, gender:'boy'},
33810 {name:'Sebastian', age:50, gender:'boy'},
33811 {name:'Erika', age:27, gender:'girl'},
33812 {name:'Patrick', age:40, gender:'boy'},
33813 {name:'Samantha', age:60, gender:'girl'}
33814 ];
33815 });
33816 </file>
33817 <file name="animations.css">
33818 .example-animate-container {
33819 background:white;
33820 border:1px solid black;
33821 list-style:none;
33822 margin:0;
33823 padding:0 10px;
33824 }
33825
33826 .animate-repeat {
33827 line-height:30px;
33828 list-style:none;
33829 box-sizing:border-box;
33830 }
33831
33832 .animate-repeat.ng-move,
33833 .animate-repeat.ng-enter,
33834 .animate-repeat.ng-leave {
33835 transition:all linear 0.5s;
33836 }
33837
33838 .animate-repeat.ng-leave.ng-leave-active,
33839 .animate-repeat.ng-move,
33840 .animate-repeat.ng-enter {
33841 opacity:0;
33842 max-height:0;
33843 }
33844
33845 .animate-repeat.ng-leave,
33846 .animate-repeat.ng-move.ng-move-active,
33847 .animate-repeat.ng-enter.ng-enter-active {
33848 opacity:1;
33849 max-height:30px;
33850 }
33851 </file>
33852 <file name="protractor.js" type="protractor">
33853 var friends = element.all(by.repeater('friend in friends'));
33854
33855 it('should render initial data set', function() {
33856 expect(friends.count()).toBe(10);
33857 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
33858 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
33859 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
33860 expect(element(by.binding('friends.length')).getText())
33861 .toMatch("I have 10 friends. They are:");
33862 });
33863
33864 it('should update repeater when filter predicate changes', function() {
33865 expect(friends.count()).toBe(10);
33866
33867 element(by.model('q')).sendKeys('ma');
33868
33869 expect(friends.count()).toBe(2);
33870 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
33871 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
33872 });
33873 </file>
33874 </example>
33875 */
33876var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) {
33877 var NG_REMOVED = '$$NG_REMOVED';
33878 var ngRepeatMinErr = minErr('ngRepeat');
33879
33880 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
33881 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
33882 scope[valueIdentifier] = value;
33883 if (keyIdentifier) scope[keyIdentifier] = key;
33884 scope.$index = index;
33885 scope.$first = (index === 0);
33886 scope.$last = (index === (arrayLength - 1));
33887 scope.$middle = !(scope.$first || scope.$last);
33888 // eslint-disable-next-line no-bitwise
33889 scope.$odd = !(scope.$even = (index & 1) === 0);
33890 };
33891
33892 var getBlockStart = function(block) {
33893 return block.clone[0];
33894 };
33895
33896 var getBlockEnd = function(block) {
33897 return block.clone[block.clone.length - 1];
33898 };
33899
33900 var trackByIdArrayFn = function($scope, key, value) {
33901 return hashKey(value);
33902 };
33903
33904 var trackByIdObjFn = function($scope, key) {
33905 return key;
33906 };
33907
33908 return {
33909 restrict: 'A',
33910 multiElement: true,
33911 transclude: 'element',
33912 priority: 1000,
33913 terminal: true,
33914 $$tlb: true,
33915 compile: function ngRepeatCompile($element, $attr) {
33916 var expression = $attr.ngRepeat;
33917 var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression);
33918
33919 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
33920
33921 if (!match) {
33922 throw ngRepeatMinErr('iexp', 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got \'{0}\'.',
33923 expression);
33924 }
33925
33926 var lhs = match[1];
33927 var rhs = match[2];
33928 var aliasAs = match[3];
33929 var trackByExp = match[4];
33930
33931 match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);
33932
33933 if (!match) {
33934 throw ngRepeatMinErr('iidexp', '\'_item_\' in \'_item_ in _collection_\' should be an identifier or \'(_key_, _value_)\' expression, but got \'{0}\'.',
33935 lhs);
33936 }
33937 var valueIdentifier = match[3] || match[1];
33938 var keyIdentifier = match[2];
33939
33940 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
33941 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
33942 throw ngRepeatMinErr('badident', 'alias \'{0}\' is invalid --- must be a valid JS identifier which is not a reserved name.',
33943 aliasAs);
33944 }
33945
33946 var trackByIdExpFn;
33947
33948 if (trackByExp) {
33949 var hashFnLocals = {$id: hashKey};
33950 var trackByExpGetter = $parse(trackByExp);
33951
33952 trackByIdExpFn = function($scope, key, value, index) {
33953 // assign key, value, and $index to the locals so that they can be used in hash functions
33954 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
33955 hashFnLocals[valueIdentifier] = value;
33956 hashFnLocals.$index = index;
33957 return trackByExpGetter($scope, hashFnLocals);
33958 };
33959 }
33960
33961 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
33962
33963 // Store a list of elements from previous run. This is a hash where key is the item from the
33964 // iterator, and the value is objects with following properties.
33965 // - scope: bound scope
33966 // - clone: previous element.
33967 // - index: position
33968 //
33969 // We are using no-proto object so that we don't need to guard against inherited props via
33970 // hasOwnProperty.
33971 var lastBlockMap = createMap();
33972
33973 //watch props
33974 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
33975 var index, length,
33976 previousNode = $element[0], // node that cloned nodes should be inserted after
33977 // initialized to the comment node anchor
33978 nextNode,
33979 // Same as lastBlockMap but it has the current state. It will become the
33980 // lastBlockMap on the next iteration.
33981 nextBlockMap = createMap(),
33982 collectionLength,
33983 key, value, // key/value of iteration
33984 trackById,
33985 trackByIdFn,
33986 collectionKeys,
33987 block, // last object information {scope, element, id}
33988 nextBlockOrder,
33989 elementsToRemove;
33990
33991 if (aliasAs) {
33992 $scope[aliasAs] = collection;
33993 }
33994
33995 if (isArrayLike(collection)) {
33996 collectionKeys = collection;
33997 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
33998 } else {
33999 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
34000 // if object, extract keys, in enumeration order, unsorted
34001 collectionKeys = [];
34002 for (var itemKey in collection) {
34003 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
34004 collectionKeys.push(itemKey);
34005 }
34006 }
34007 }
34008
34009 collectionLength = collectionKeys.length;
34010 nextBlockOrder = new Array(collectionLength);
34011
34012 // locate existing items
34013 for (index = 0; index < collectionLength; index++) {
34014 key = (collection === collectionKeys) ? index : collectionKeys[index];
34015 value = collection[key];
34016 trackById = trackByIdFn($scope, key, value, index);
34017 if (lastBlockMap[trackById]) {
34018 // found previously seen block
34019 block = lastBlockMap[trackById];
34020 delete lastBlockMap[trackById];
34021 nextBlockMap[trackById] = block;
34022 nextBlockOrder[index] = block;
34023 } else if (nextBlockMap[trackById]) {
34024 // if collision detected. restore lastBlockMap and throw an error
34025 forEach(nextBlockOrder, function(block) {
34026 if (block && block.scope) lastBlockMap[block.id] = block;
34027 });
34028 throw ngRepeatMinErr('dupes',
34029 'Duplicates in a repeater are not allowed. Use \'track by\' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}',
34030 expression, trackById, value);
34031 } else {
34032 // new never before seen block
34033 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
34034 nextBlockMap[trackById] = true;
34035 }
34036 }
34037
34038 // Clear the value property from the hashFnLocals object to prevent a reference to the last value
34039 // being leaked into the ngRepeatCompile function scope
34040 if (hashFnLocals) {
34041 hashFnLocals[valueIdentifier] = undefined;
34042 }
34043
34044 // remove leftover items
34045 for (var blockKey in lastBlockMap) {
34046 block = lastBlockMap[blockKey];
34047 elementsToRemove = getBlockNodes(block.clone);
34048 $animate.leave(elementsToRemove);
34049 if (elementsToRemove[0].parentNode) {
34050 // if the element was not removed yet because of pending animation, mark it as deleted
34051 // so that we can ignore it later
34052 for (index = 0, length = elementsToRemove.length; index < length; index++) {
34053 elementsToRemove[index][NG_REMOVED] = true;
34054 }
34055 }
34056 block.scope.$destroy();
34057 }
34058
34059 // we are not using forEach for perf reasons (trying to avoid #call)
34060 for (index = 0; index < collectionLength; index++) {
34061 key = (collection === collectionKeys) ? index : collectionKeys[index];
34062 value = collection[key];
34063 block = nextBlockOrder[index];
34064
34065 if (block.scope) {
34066 // if we have already seen this object, then we need to reuse the
34067 // associated scope/element
34068
34069 nextNode = previousNode;
34070
34071 // skip nodes that are already pending removal via leave animation
34072 do {
34073 nextNode = nextNode.nextSibling;
34074 } while (nextNode && nextNode[NG_REMOVED]);
34075
34076 if (getBlockStart(block) !== nextNode) {
34077 // existing item which got moved
34078 $animate.move(getBlockNodes(block.clone), null, previousNode);
34079 }
34080 previousNode = getBlockEnd(block);
34081 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
34082 } else {
34083 // new item which we don't know about
34084 $transclude(function ngRepeatTransclude(clone, scope) {
34085 block.scope = scope;
34086 // http://jsperf.com/clone-vs-createcomment
34087 var endNode = ngRepeatEndComment.cloneNode(false);
34088 clone[clone.length++] = endNode;
34089
34090 $animate.enter(clone, null, previousNode);
34091 previousNode = endNode;
34092 // Note: We only need the first/last node of the cloned nodes.
34093 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
34094 // by a directive with templateUrl when its template arrives.
34095 block.clone = clone;
34096 nextBlockMap[block.id] = block;
34097 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
34098 });
34099 }
34100 }
34101 lastBlockMap = nextBlockMap;
34102 });
34103 };
34104 }
34105 };
34106}];
34107
34108var NG_HIDE_CLASS = 'ng-hide';
34109var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
34110/**
34111 * @ngdoc directive
34112 * @name ngShow
34113 * @multiElement
34114 *
34115 * @description
34116 * The `ngShow` directive shows or hides the given HTML element based on the expression provided to
34117 * the `ngShow` attribute.
34118 *
34119 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
34120 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
34121 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
34122 * {@link ng.directive:ngCsp ngCsp}).
34123 *
34124 * ```html
34125 * <!-- when $scope.myValue is truthy (element is visible) -->
34126 * <div ng-show="myValue"></div>
34127 *
34128 * <!-- when $scope.myValue is falsy (element is hidden) -->
34129 * <div ng-show="myValue" class="ng-hide"></div>
34130 * ```
34131 *
34132 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added
34133 * to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide`
34134 * CSS class is removed from the element causing the element not to appear hidden.
34135 *
34136 * ## Why is `!important` used?
34137 *
34138 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
34139 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
34140 * simple as changing the display style on a HTML list item would make hidden elements appear
34141 * visible. This also becomes a bigger issue when dealing with CSS frameworks.
34142 *
34143 * By using `!important`, the show and hide behavior will work as expected despite any clash between
34144 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
34145 * developer chooses to override the styling to change how to hide an element then it is just a
34146 * matter of using `!important` in their own CSS code.
34147 *
34148 * ### Overriding `.ng-hide`
34149 *
34150 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you
34151 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
34152 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
34153 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
34154 *
34155 * ```css
34156 * .ng-hide:not(.ng-hide-animate) {
34157 * /&#42; These are just alternative ways of hiding an element &#42;/
34158 * display: block!important;
34159 * position: absolute;
34160 * top: -9999px;
34161 * left: -9999px;
34162 * }
34163 * ```
34164 *
34165 * By default you don't need to override anything in CSS and the animations will work around the
34166 * display style.
34167 *
34168 * @animations
34169 * | Animation | Occurs |
34170 * |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
34171 * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden. |
34172 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible. |
34173 *
34174 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
34175 * directive expression is true and false. This system works like the animation system present with
34176 * `ngClass` except that you must also include the `!important` flag to override the display
34177 * property so that the elements are not actually hidden during the animation.
34178 *
34179 * ```css
34180 * /&#42; A working example can be found at the bottom of this page. &#42;/
34181 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
34182 * transition: all 0.5s linear;
34183 * }
34184 *
34185 * .my-element.ng-hide-add { ... }
34186 * .my-element.ng-hide-add.ng-hide-add-active { ... }
34187 * .my-element.ng-hide-remove { ... }
34188 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
34189 * ```
34190 *
34191 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
34192 * to block during animation states - ngAnimate will automatically handle the style toggling for you.
34193 *
34194 * @element ANY
34195 * @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the
34196 * element is shown/hidden respectively.
34197 *
34198 * @example
34199 * A simple example, animating the element's opacity:
34200 *
34201 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-simple">
34202 <file name="index.html">
34203 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
34204 <div class="check-element animate-show-hide" ng-show="checked">
34205 I show up when your checkbox is checked.
34206 </div>
34207 </file>
34208 <file name="animations.css">
34209 .animate-show-hide.ng-hide {
34210 opacity: 0;
34211 }
34212
34213 .animate-show-hide.ng-hide-add,
34214 .animate-show-hide.ng-hide-remove {
34215 transition: all linear 0.5s;
34216 }
34217
34218 .check-element {
34219 border: 1px solid black;
34220 opacity: 1;
34221 padding: 10px;
34222 }
34223 </file>
34224 <file name="protractor.js" type="protractor">
34225 it('should check ngShow', function() {
34226 var checkbox = element(by.model('checked'));
34227 var checkElem = element(by.css('.check-element'));
34228
34229 expect(checkElem.isDisplayed()).toBe(false);
34230 checkbox.click();
34231 expect(checkElem.isDisplayed()).toBe(true);
34232 });
34233 </file>
34234 </example>
34235 *
34236 * <hr />
34237 * @example
34238 * A more complex example, featuring different show/hide animations:
34239 *
34240 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-complex">
34241 <file name="index.html">
34242 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
34243 <div class="check-element funky-show-hide" ng-show="checked">
34244 I show up when your checkbox is checked.
34245 </div>
34246 </file>
34247 <file name="animations.css">
34248 body {
34249 overflow: hidden;
34250 perspective: 1000px;
34251 }
34252
34253 .funky-show-hide.ng-hide-add {
34254 transform: rotateZ(0);
34255 transform-origin: right;
34256 transition: all 0.5s ease-in-out;
34257 }
34258
34259 .funky-show-hide.ng-hide-add.ng-hide-add-active {
34260 transform: rotateZ(-135deg);
34261 }
34262
34263 .funky-show-hide.ng-hide-remove {
34264 transform: rotateY(90deg);
34265 transform-origin: left;
34266 transition: all 0.5s ease;
34267 }
34268
34269 .funky-show-hide.ng-hide-remove.ng-hide-remove-active {
34270 transform: rotateY(0);
34271 }
34272
34273 .check-element {
34274 border: 1px solid black;
34275 opacity: 1;
34276 padding: 10px;
34277 }
34278 </file>
34279 <file name="protractor.js" type="protractor">
34280 it('should check ngShow', function() {
34281 var checkbox = element(by.model('checked'));
34282 var checkElem = element(by.css('.check-element'));
34283
34284 expect(checkElem.isDisplayed()).toBe(false);
34285 checkbox.click();
34286 expect(checkElem.isDisplayed()).toBe(true);
34287 });
34288 </file>
34289 </example>
34290 *
34291 * @knownIssue
34292 *
34293 * ### Flickering when using ngShow to toggle between elements
34294 *
34295 * When using {@link ngShow} and / or {@link ngHide} to toggle between elements, it can
34296 * happen that both the element to show and the element to hide are visible for a very short time.
34297 *
34298 * This usually happens when the {@link ngAnimate ngAnimate module} is included, but no actual animations
34299 * are defined for {@link ngShow} / {@link ngHide}. Internet Explorer is affected more often than
34300 * other browsers.
34301 *
34302 * There are several way to mitigate this problem:
34303 *
34304 * - {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations Disable animations on the affected elements}.
34305 * - Use {@link ngIf} or {@link ngSwitch} instead of {@link ngShow} / {@link ngHide}.
34306 * - Use the special CSS selector `ng-hide.ng-hide-animate` to set `{display: none}` or similar on the affected elements.
34307 * - Use `ng-class="{'ng-hide': expression}` instead of instead of {@link ngShow} / {@link ngHide}.
34308 * - Define an animation on the affected elements.
34309 */
34310var ngShowDirective = ['$animate', function($animate) {
34311 return {
34312 restrict: 'A',
34313 multiElement: true,
34314 link: function(scope, element, attr) {
34315 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
34316 // we're adding a temporary, animation-specific class for ng-hide since this way
34317 // we can control when the element is actually displayed on screen without having
34318 // to have a global/greedy CSS selector that breaks when other animations are run.
34319 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
34320 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
34321 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
34322 });
34323 });
34324 }
34325 };
34326}];
34327
34328
34329/**
34330 * @ngdoc directive
34331 * @name ngHide
34332 * @multiElement
34333 *
34334 * @description
34335 * The `ngHide` directive shows or hides the given HTML element based on the expression provided to
34336 * the `ngHide` attribute.
34337 *
34338 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
34339 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
34340 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
34341 * {@link ng.directive:ngCsp ngCsp}).
34342 *
34343 * ```html
34344 * <!-- when $scope.myValue is truthy (element is hidden) -->
34345 * <div ng-hide="myValue" class="ng-hide"></div>
34346 *
34347 * <!-- when $scope.myValue is falsy (element is visible) -->
34348 * <div ng-hide="myValue"></div>
34349 * ```
34350 *
34351 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added
34352 * to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide`
34353 * CSS class is removed from the element causing the element not to appear hidden.
34354 *
34355 * ## Why is `!important` used?
34356 *
34357 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
34358 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
34359 * simple as changing the display style on a HTML list item would make hidden elements appear
34360 * visible. This also becomes a bigger issue when dealing with CSS frameworks.
34361 *
34362 * By using `!important`, the show and hide behavior will work as expected despite any clash between
34363 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
34364 * developer chooses to override the styling to change how to hide an element then it is just a
34365 * matter of using `!important` in their own CSS code.
34366 *
34367 * ### Overriding `.ng-hide`
34368 *
34369 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you
34370 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
34371 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
34372 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
34373 *
34374 * ```css
34375 * .ng-hide:not(.ng-hide-animate) {
34376 * /&#42; These are just alternative ways of hiding an element &#42;/
34377 * display: block!important;
34378 * position: absolute;
34379 * top: -9999px;
34380 * left: -9999px;
34381 * }
34382 * ```
34383 *
34384 * By default you don't need to override in CSS anything and the animations will work around the
34385 * display style.
34386 *
34387 * @animations
34388 * | Animation | Occurs |
34389 * |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------|
34390 * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden. |
34391 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. |
34392 *
34393 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
34394 * directive expression is true and false. This system works like the animation system present with
34395 * `ngClass` except that you must also include the `!important` flag to override the display
34396 * property so that the elements are not actually hidden during the animation.
34397 *
34398 * ```css
34399 * /&#42; A working example can be found at the bottom of this page. &#42;/
34400 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
34401 * transition: all 0.5s linear;
34402 * }
34403 *
34404 * .my-element.ng-hide-add { ... }
34405 * .my-element.ng-hide-add.ng-hide-add-active { ... }
34406 * .my-element.ng-hide-remove { ... }
34407 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
34408 * ```
34409 *
34410 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
34411 * to block during animation states - ngAnimate will automatically handle the style toggling for you.
34412 *
34413 * @element ANY
34414 * @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the
34415 * element is hidden/shown respectively.
34416 *
34417 * @example
34418 * A simple example, animating the element's opacity:
34419 *
34420 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-simple">
34421 <file name="index.html">
34422 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
34423 <div class="check-element animate-show-hide" ng-hide="checked">
34424 I hide when your checkbox is checked.
34425 </div>
34426 </file>
34427 <file name="animations.css">
34428 .animate-show-hide.ng-hide {
34429 opacity: 0;
34430 }
34431
34432 .animate-show-hide.ng-hide-add,
34433 .animate-show-hide.ng-hide-remove {
34434 transition: all linear 0.5s;
34435 }
34436
34437 .check-element {
34438 border: 1px solid black;
34439 opacity: 1;
34440 padding: 10px;
34441 }
34442 </file>
34443 <file name="protractor.js" type="protractor">
34444 it('should check ngHide', function() {
34445 var checkbox = element(by.model('checked'));
34446 var checkElem = element(by.css('.check-element'));
34447
34448 expect(checkElem.isDisplayed()).toBe(true);
34449 checkbox.click();
34450 expect(checkElem.isDisplayed()).toBe(false);
34451 });
34452 </file>
34453 </example>
34454 *
34455 * <hr />
34456 * @example
34457 * A more complex example, featuring different show/hide animations:
34458 *
34459 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-complex">
34460 <file name="index.html">
34461 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
34462 <div class="check-element funky-show-hide" ng-hide="checked">
34463 I hide when your checkbox is checked.
34464 </div>
34465 </file>
34466 <file name="animations.css">
34467 body {
34468 overflow: hidden;
34469 perspective: 1000px;
34470 }
34471
34472 .funky-show-hide.ng-hide-add {
34473 transform: rotateZ(0);
34474 transform-origin: right;
34475 transition: all 0.5s ease-in-out;
34476 }
34477
34478 .funky-show-hide.ng-hide-add.ng-hide-add-active {
34479 transform: rotateZ(-135deg);
34480 }
34481
34482 .funky-show-hide.ng-hide-remove {
34483 transform: rotateY(90deg);
34484 transform-origin: left;
34485 transition: all 0.5s ease;
34486 }
34487
34488 .funky-show-hide.ng-hide-remove.ng-hide-remove-active {
34489 transform: rotateY(0);
34490 }
34491
34492 .check-element {
34493 border: 1px solid black;
34494 opacity: 1;
34495 padding: 10px;
34496 }
34497 </file>
34498 <file name="protractor.js" type="protractor">
34499 it('should check ngHide', function() {
34500 var checkbox = element(by.model('checked'));
34501 var checkElem = element(by.css('.check-element'));
34502
34503 expect(checkElem.isDisplayed()).toBe(true);
34504 checkbox.click();
34505 expect(checkElem.isDisplayed()).toBe(false);
34506 });
34507 </file>
34508 </example>
34509 *
34510 * @knownIssue
34511 *
34512 * ### Flickering when using ngHide to toggle between elements
34513 *
34514 * When using {@link ngShow} and / or {@link ngHide} to toggle between elements, it can
34515 * happen that both the element to show and the element to hide are visible for a very short time.
34516 *
34517 * This usually happens when the {@link ngAnimate ngAnimate module} is included, but no actual animations
34518 * are defined for {@link ngShow} / {@link ngHide}. Internet Explorer is affected more often than
34519 * other browsers.
34520 *
34521 * There are several way to mitigate this problem:
34522 *
34523 * - {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations Disable animations on the affected elements}.
34524 * - Use {@link ngIf} or {@link ngSwitch} instead of {@link ngShow} / {@link ngHide}.
34525 * - Use the special CSS selector `ng-hide.ng-hide-animate` to set `{display: none}` or similar on the affected elements.
34526 * - Use `ng-class="{'ng-hide': expression}` instead of instead of {@link ngShow} / {@link ngHide}.
34527 * - Define an animation on the affected elements.
34528 */
34529var ngHideDirective = ['$animate', function($animate) {
34530 return {
34531 restrict: 'A',
34532 multiElement: true,
34533 link: function(scope, element, attr) {
34534 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
34535 // The comment inside of the ngShowDirective explains why we add and
34536 // remove a temporary class for the show/hide animation
34537 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
34538 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
34539 });
34540 });
34541 }
34542 };
34543}];
34544
34545/**
34546 * @ngdoc directive
34547 * @name ngStyle
34548 * @restrict AC
34549 *
34550 * @description
34551 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
34552 *
34553 * @knownIssue
34554 * You should not use {@link guide/interpolation interpolation} in the value of the `style`
34555 * attribute, when using the `ngStyle` directive on the same element.
34556 * See {@link guide/interpolation#known-issues here} for more info.
34557 *
34558 * @element ANY
34559 * @param {expression} ngStyle
34560 *
34561 * {@link guide/expression Expression} which evals to an
34562 * object whose keys are CSS style names and values are corresponding values for those CSS
34563 * keys.
34564 *
34565 * Since some CSS style names are not valid keys for an object, they must be quoted.
34566 * See the 'background-color' style in the example below.
34567 *
34568 * @example
34569 <example name="ng-style">
34570 <file name="index.html">
34571 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
34572 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
34573 <input type="button" value="clear" ng-click="myStyle={}">
34574 <br/>
34575 <span ng-style="myStyle">Sample Text</span>
34576 <pre>myStyle={{myStyle}}</pre>
34577 </file>
34578 <file name="style.css">
34579 span {
34580 color: black;
34581 }
34582 </file>
34583 <file name="protractor.js" type="protractor">
34584 var colorSpan = element(by.css('span'));
34585
34586 it('should check ng-style', function() {
34587 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(0, 0, 0, 1\)|rgb\(0, 0, 0\)/);
34588 element(by.css('input[value=\'set color\']')).click();
34589 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(255, 0, 0, 1\)|rgb\(255, 0, 0\)/);
34590 element(by.css('input[value=clear]')).click();
34591 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(0, 0, 0, 1\)|rgb\(0, 0, 0\)/);
34592 });
34593 </file>
34594 </example>
34595 */
34596var ngStyleDirective = ngDirective(function(scope, element, attr) {
34597 scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
34598 if (oldStyles && (newStyles !== oldStyles)) {
34599 forEach(oldStyles, function(val, style) { element.css(style, ''); });
34600 }
34601 if (newStyles) element.css(newStyles);
34602 });
34603});
34604
34605/**
34606 * @ngdoc directive
34607 * @name ngSwitch
34608 * @restrict EA
34609 *
34610 * @description
34611 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
34612 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
34613 * as specified in the template.
34614 *
34615 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
34616 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
34617 * matches the value obtained from the evaluated expression. In other words, you define a container element
34618 * (where you place the directive), place an expression on the **`on="..."` attribute**
34619 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
34620 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
34621 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
34622 * attribute is displayed.
34623 *
34624 * <div class="alert alert-info">
34625 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
34626 * as literal string values to match against.
34627 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
34628 * value of the expression `$scope.someVal`.
34629 * </div>
34630
34631 * @animations
34632 * | Animation | Occurs |
34633 * |----------------------------------|-------------------------------------|
34634 * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container |
34635 * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM |
34636 *
34637 * @usage
34638 *
34639 * ```
34640 * <ANY ng-switch="expression">
34641 * <ANY ng-switch-when="matchValue1">...</ANY>
34642 * <ANY ng-switch-when="matchValue2">...</ANY>
34643 * <ANY ng-switch-default>...</ANY>
34644 * </ANY>
34645 * ```
34646 *
34647 *
34648 * @scope
34649 * @priority 1200
34650 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
34651 * On child elements add:
34652 *
34653 * * `ngSwitchWhen`: the case statement to match against. If match then this
34654 * case will be displayed. If the same match appears multiple times, all the
34655 * elements will be displayed. It is possible to associate multiple values to
34656 * the same `ngSwitchWhen` by defining the optional attribute
34657 * `ngSwitchWhenSeparator`. The separator will be used to split the value of
34658 * the `ngSwitchWhen` attribute into multiple tokens, and the element will show
34659 * if any of the `ngSwitch` evaluates to any of these tokens.
34660 * * `ngSwitchDefault`: the default case when no other case match. If there
34661 * are multiple default cases, all of them will be displayed when no other
34662 * case match.
34663 *
34664 *
34665 * @example
34666 <example module="switchExample" deps="angular-animate.js" animations="true" name="ng-switch">
34667 <file name="index.html">
34668 <div ng-controller="ExampleController">
34669 <select ng-model="selection" ng-options="item for item in items">
34670 </select>
34671 <code>selection={{selection}}</code>
34672 <hr/>
34673 <div class="animate-switch-container"
34674 ng-switch on="selection">
34675 <div class="animate-switch" ng-switch-when="settings|options" ng-switch-when-separator="|">Settings Div</div>
34676 <div class="animate-switch" ng-switch-when="home">Home Span</div>
34677 <div class="animate-switch" ng-switch-default>default</div>
34678 </div>
34679 </div>
34680 </file>
34681 <file name="script.js">
34682 angular.module('switchExample', ['ngAnimate'])
34683 .controller('ExampleController', ['$scope', function($scope) {
34684 $scope.items = ['settings', 'home', 'options', 'other'];
34685 $scope.selection = $scope.items[0];
34686 }]);
34687 </file>
34688 <file name="animations.css">
34689 .animate-switch-container {
34690 position:relative;
34691 background:white;
34692 border:1px solid black;
34693 height:40px;
34694 overflow:hidden;
34695 }
34696
34697 .animate-switch {
34698 padding:10px;
34699 }
34700
34701 .animate-switch.ng-animate {
34702 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
34703
34704 position:absolute;
34705 top:0;
34706 left:0;
34707 right:0;
34708 bottom:0;
34709 }
34710
34711 .animate-switch.ng-leave.ng-leave-active,
34712 .animate-switch.ng-enter {
34713 top:-50px;
34714 }
34715 .animate-switch.ng-leave,
34716 .animate-switch.ng-enter.ng-enter-active {
34717 top:0;
34718 }
34719 </file>
34720 <file name="protractor.js" type="protractor">
34721 var switchElem = element(by.css('[ng-switch]'));
34722 var select = element(by.model('selection'));
34723
34724 it('should start in settings', function() {
34725 expect(switchElem.getText()).toMatch(/Settings Div/);
34726 });
34727 it('should change to home', function() {
34728 select.all(by.css('option')).get(1).click();
34729 expect(switchElem.getText()).toMatch(/Home Span/);
34730 });
34731 it('should change to settings via "options"', function() {
34732 select.all(by.css('option')).get(2).click();
34733 expect(switchElem.getText()).toMatch(/Settings Div/);
34734 });
34735 it('should select default', function() {
34736 select.all(by.css('option')).get(3).click();
34737 expect(switchElem.getText()).toMatch(/default/);
34738 });
34739 </file>
34740 </example>
34741 */
34742var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) {
34743 return {
34744 require: 'ngSwitch',
34745
34746 // asks for $scope to fool the BC controller module
34747 controller: ['$scope', function NgSwitchController() {
34748 this.cases = {};
34749 }],
34750 link: function(scope, element, attr, ngSwitchController) {
34751 var watchExpr = attr.ngSwitch || attr.on,
34752 selectedTranscludes = [],
34753 selectedElements = [],
34754 previousLeaveAnimations = [],
34755 selectedScopes = [];
34756
34757 var spliceFactory = function(array, index) {
34758 return function(response) {
34759 if (response !== false) array.splice(index, 1);
34760 };
34761 };
34762
34763 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
34764 var i, ii;
34765
34766 // Start with the last, in case the array is modified during the loop
34767 while (previousLeaveAnimations.length) {
34768 $animate.cancel(previousLeaveAnimations.pop());
34769 }
34770
34771 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
34772 var selected = getBlockNodes(selectedElements[i].clone);
34773 selectedScopes[i].$destroy();
34774 var runner = previousLeaveAnimations[i] = $animate.leave(selected);
34775 runner.done(spliceFactory(previousLeaveAnimations, i));
34776 }
34777
34778 selectedElements.length = 0;
34779 selectedScopes.length = 0;
34780
34781 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
34782 forEach(selectedTranscludes, function(selectedTransclude) {
34783 selectedTransclude.transclude(function(caseElement, selectedScope) {
34784 selectedScopes.push(selectedScope);
34785 var anchor = selectedTransclude.element;
34786 caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen');
34787 var block = { clone: caseElement };
34788
34789 selectedElements.push(block);
34790 $animate.enter(caseElement, anchor.parent(), anchor);
34791 });
34792 });
34793 }
34794 });
34795 }
34796 };
34797}];
34798
34799var ngSwitchWhenDirective = ngDirective({
34800 transclude: 'element',
34801 priority: 1200,
34802 require: '^ngSwitch',
34803 multiElement: true,
34804 link: function(scope, element, attrs, ctrl, $transclude) {
34805
34806 var cases = attrs.ngSwitchWhen.split(attrs.ngSwitchWhenSeparator).sort().filter(
34807 // Filter duplicate cases
34808 function(element, index, array) { return array[index - 1] !== element; }
34809 );
34810
34811 forEach(cases, function(whenCase) {
34812 ctrl.cases['!' + whenCase] = (ctrl.cases['!' + whenCase] || []);
34813 ctrl.cases['!' + whenCase].push({ transclude: $transclude, element: element });
34814 });
34815 }
34816});
34817
34818var ngSwitchDefaultDirective = ngDirective({
34819 transclude: 'element',
34820 priority: 1200,
34821 require: '^ngSwitch',
34822 multiElement: true,
34823 link: function(scope, element, attr, ctrl, $transclude) {
34824 ctrl.cases['?'] = (ctrl.cases['?'] || []);
34825 ctrl.cases['?'].push({ transclude: $transclude, element: element });
34826 }
34827});
34828
34829/**
34830 * @ngdoc directive
34831 * @name ngTransclude
34832 * @restrict EAC
34833 *
34834 * @description
34835 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
34836 *
34837 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name
34838 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute.
34839 *
34840 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing
34841 * content of this element will be removed before the transcluded content is inserted.
34842 * If the transcluded content is empty (or only whitespace), the existing content is left intact. This lets you provide fallback
34843 * content in the case that no transcluded content is provided.
34844 *
34845 * @element ANY
34846 *
34847 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty
34848 * or its value is the same as the name of the attribute then the default slot is used.
34849 *
34850 * @example
34851 * ### Basic transclusion
34852 * This example demonstrates basic transclusion of content into a component directive.
34853 * <example name="simpleTranscludeExample" module="transcludeExample">
34854 * <file name="index.html">
34855 * <script>
34856 * angular.module('transcludeExample', [])
34857 * .directive('pane', function(){
34858 * return {
34859 * restrict: 'E',
34860 * transclude: true,
34861 * scope: { title:'@' },
34862 * template: '<div style="border: 1px solid black;">' +
34863 * '<div style="background-color: gray">{{title}}</div>' +
34864 * '<ng-transclude></ng-transclude>' +
34865 * '</div>'
34866 * };
34867 * })
34868 * .controller('ExampleController', ['$scope', function($scope) {
34869 * $scope.title = 'Lorem Ipsum';
34870 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
34871 * }]);
34872 * </script>
34873 * <div ng-controller="ExampleController">
34874 * <input ng-model="title" aria-label="title"> <br/>
34875 * <textarea ng-model="text" aria-label="text"></textarea> <br/>
34876 * <pane title="{{title}}"><span>{{text}}</span></pane>
34877 * </div>
34878 * </file>
34879 * <file name="protractor.js" type="protractor">
34880 * it('should have transcluded', function() {
34881 * var titleElement = element(by.model('title'));
34882 * titleElement.clear();
34883 * titleElement.sendKeys('TITLE');
34884 * var textElement = element(by.model('text'));
34885 * textElement.clear();
34886 * textElement.sendKeys('TEXT');
34887 * expect(element(by.binding('title')).getText()).toEqual('TITLE');
34888 * expect(element(by.binding('text')).getText()).toEqual('TEXT');
34889 * });
34890 * </file>
34891 * </example>
34892 *
34893 * @example
34894 * ### Transclude fallback content
34895 * This example shows how to use `NgTransclude` with fallback content, that
34896 * is displayed if no transcluded content is provided.
34897 *
34898 * <example module="transcludeFallbackContentExample" name="ng-transclude">
34899 * <file name="index.html">
34900 * <script>
34901 * angular.module('transcludeFallbackContentExample', [])
34902 * .directive('myButton', function(){
34903 * return {
34904 * restrict: 'E',
34905 * transclude: true,
34906 * scope: true,
34907 * template: '<button style="cursor: pointer;">' +
34908 * '<ng-transclude>' +
34909 * '<b style="color: red;">Button1</b>' +
34910 * '</ng-transclude>' +
34911 * '</button>'
34912 * };
34913 * });
34914 * </script>
34915 * <!-- fallback button content -->
34916 * <my-button id="fallback"></my-button>
34917 * <!-- modified button content -->
34918 * <my-button id="modified">
34919 * <i style="color: green;">Button2</i>
34920 * </my-button>
34921 * </file>
34922 * <file name="protractor.js" type="protractor">
34923 * it('should have different transclude element content', function() {
34924 * expect(element(by.id('fallback')).getText()).toBe('Button1');
34925 * expect(element(by.id('modified')).getText()).toBe('Button2');
34926 * });
34927 * </file>
34928 * </example>
34929 *
34930 * @example
34931 * ### Multi-slot transclusion
34932 * This example demonstrates using multi-slot transclusion in a component directive.
34933 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample">
34934 * <file name="index.html">
34935 * <style>
34936 * .title, .footer {
34937 * background-color: gray
34938 * }
34939 * </style>
34940 * <div ng-controller="ExampleController">
34941 * <input ng-model="title" aria-label="title"> <br/>
34942 * <textarea ng-model="text" aria-label="text"></textarea> <br/>
34943 * <pane>
34944 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title>
34945 * <pane-body><p>{{text}}</p></pane-body>
34946 * </pane>
34947 * </div>
34948 * </file>
34949 * <file name="app.js">
34950 * angular.module('multiSlotTranscludeExample', [])
34951 * .directive('pane', function() {
34952 * return {
34953 * restrict: 'E',
34954 * transclude: {
34955 * 'title': '?paneTitle',
34956 * 'body': 'paneBody',
34957 * 'footer': '?paneFooter'
34958 * },
34959 * template: '<div style="border: 1px solid black;">' +
34960 * '<div class="title" ng-transclude="title">Fallback Title</div>' +
34961 * '<div ng-transclude="body"></div>' +
34962 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
34963 * '</div>'
34964 * };
34965 * })
34966 * .controller('ExampleController', ['$scope', function($scope) {
34967 * $scope.title = 'Lorem Ipsum';
34968 * $scope.link = 'https://google.com';
34969 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
34970 * }]);
34971 * </file>
34972 * <file name="protractor.js" type="protractor">
34973 * it('should have transcluded the title and the body', function() {
34974 * var titleElement = element(by.model('title'));
34975 * titleElement.clear();
34976 * titleElement.sendKeys('TITLE');
34977 * var textElement = element(by.model('text'));
34978 * textElement.clear();
34979 * textElement.sendKeys('TEXT');
34980 * expect(element(by.css('.title')).getText()).toEqual('TITLE');
34981 * expect(element(by.binding('text')).getText()).toEqual('TEXT');
34982 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer');
34983 * });
34984 * </file>
34985 * </example>
34986 */
34987var ngTranscludeMinErr = minErr('ngTransclude');
34988var ngTranscludeDirective = ['$compile', function($compile) {
34989 return {
34990 restrict: 'EAC',
34991 compile: function ngTranscludeCompile(tElement) {
34992
34993 // Remove and cache any original content to act as a fallback
34994 var fallbackLinkFn = $compile(tElement.contents());
34995 tElement.empty();
34996
34997 return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) {
34998
34999 if (!$transclude) {
35000 throw ngTranscludeMinErr('orphan',
35001 'Illegal use of ngTransclude directive in the template! ' +
35002 'No parent directive that requires a transclusion found. ' +
35003 'Element: {0}',
35004 startingTag($element));
35005 }
35006
35007
35008 // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default
35009 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
35010 $attrs.ngTransclude = '';
35011 }
35012 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;
35013
35014 // If the slot is required and no transclusion content is provided then this call will throw an error
35015 $transclude(ngTranscludeCloneAttachFn, null, slotName);
35016
35017 // If the slot is optional and no transclusion content is provided then use the fallback content
35018 if (slotName && !$transclude.isSlotFilled(slotName)) {
35019 useFallbackContent();
35020 }
35021
35022 function ngTranscludeCloneAttachFn(clone, transcludedScope) {
35023 if (clone.length && notWhitespace(clone)) {
35024 $element.append(clone);
35025 } else {
35026 useFallbackContent();
35027 // There is nothing linked against the transcluded scope since no content was available,
35028 // so it should be safe to clean up the generated scope.
35029 transcludedScope.$destroy();
35030 }
35031 }
35032
35033 function useFallbackContent() {
35034 // Since this is the fallback content rather than the transcluded content,
35035 // we link against the scope of this directive rather than the transcluded scope
35036 fallbackLinkFn($scope, function(clone) {
35037 $element.append(clone);
35038 });
35039 }
35040
35041 function notWhitespace(nodes) {
35042 for (var i = 0, ii = nodes.length; i < ii; i++) {
35043 var node = nodes[i];
35044 if (node.nodeType !== NODE_TYPE_TEXT || node.nodeValue.trim()) {
35045 return true;
35046 }
35047 }
35048 }
35049 };
35050 }
35051 };
35052}];
35053
35054/**
35055 * @ngdoc directive
35056 * @name script
35057 * @restrict E
35058 *
35059 * @description
35060 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
35061 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
35062 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
35063 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
35064 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
35065 *
35066 * @param {string} type Must be set to `'text/ng-template'`.
35067 * @param {string} id Cache name of the template.
35068 *
35069 * @example
35070 <example name="script-tag">
35071 <file name="index.html">
35072 <script type="text/ng-template" id="/tpl.html">
35073 Content of the template.
35074 </script>
35075
35076 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
35077 <div id="tpl-content" ng-include src="currentTpl"></div>
35078 </file>
35079 <file name="protractor.js" type="protractor">
35080 it('should load template defined inside script tag', function() {
35081 element(by.css('#tpl-link')).click();
35082 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
35083 });
35084 </file>
35085 </example>
35086 */
35087var scriptDirective = ['$templateCache', function($templateCache) {
35088 return {
35089 restrict: 'E',
35090 terminal: true,
35091 compile: function(element, attr) {
35092 if (attr.type === 'text/ng-template') {
35093 var templateUrl = attr.id,
35094 text = element[0].text;
35095
35096 $templateCache.put(templateUrl, text);
35097 }
35098 }
35099 };
35100}];
35101
35102/* exported selectDirective, optionDirective */
35103
35104var noopNgModelController = { $setViewValue: noop, $render: noop };
35105
35106function setOptionSelectedStatus(optionEl, value) {
35107 optionEl.prop('selected', value);
35108 /**
35109 * When unselecting an option, setting the property to null / false should be enough
35110 * However, screenreaders might react to the selected attribute instead, see
35111 * https://github.com/angular/angular.js/issues/14419
35112 * Note: "selected" is a boolean attr and will be removed when the "value" arg in attr() is false
35113 * or null
35114 */
35115 optionEl.attr('selected', value);
35116}
35117
35118/**
35119 * @ngdoc type
35120 * @name select.SelectController
35121 *
35122 * @description
35123 * The controller for the {@link ng.select select} directive. The controller exposes
35124 * a few utility methods that can be used to augment the behavior of a regular or an
35125 * {@link ng.ngOptions ngOptions} select element.
35126 *
35127 * @example
35128 * ### Set a custom error when the unknown option is selected
35129 *
35130 * This example sets a custom error "unknownValue" on the ngModelController
35131 * when the select element's unknown option is selected, i.e. when the model is set to a value
35132 * that is not matched by any option.
35133 *
35134 * <example name="select-unknown-value-error" module="staticSelect">
35135 * <file name="index.html">
35136 * <div ng-controller="ExampleController">
35137 * <form name="myForm">
35138 * <label for="testSelect"> Single select: </label><br>
35139 * <select name="testSelect" ng-model="selected" unknown-value-error>
35140 * <option value="option-1">Option 1</option>
35141 * <option value="option-2">Option 2</option>
35142 * </select><br>
35143 * <span class="error" ng-if="myForm.testSelect.$error.unknownValue">
35144 * Error: The current model doesn't match any option</span><br>
35145 *
35146 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
35147 * </form>
35148 * </div>
35149 * </file>
35150 * <file name="app.js">
35151 * angular.module('staticSelect', [])
35152 * .controller('ExampleController', ['$scope', function($scope) {
35153 * $scope.selected = null;
35154 *
35155 * $scope.forceUnknownOption = function() {
35156 * $scope.selected = 'nonsense';
35157 * };
35158 * }])
35159 * .directive('unknownValueError', function() {
35160 * return {
35161 * require: ['ngModel', 'select'],
35162 * link: function(scope, element, attrs, ctrls) {
35163 * var ngModelCtrl = ctrls[0];
35164 * var selectCtrl = ctrls[1];
35165 *
35166 * ngModelCtrl.$validators.unknownValue = function(modelValue, viewValue) {
35167 * if (selectCtrl.$isUnknownOptionSelected()) {
35168 * return false;
35169 * }
35170 *
35171 * return true;
35172 * };
35173 * }
35174 *
35175 * };
35176 * });
35177 * </file>
35178 *</example>
35179 *
35180 *
35181 * @example
35182 * ### Set the "required" error when the unknown option is selected.
35183 *
35184 * By default, the "required" error on the ngModelController is only set on a required select
35185 * when the empty option is selected. This example adds a custom directive that also sets the
35186 * error when the unknown option is selected.
35187 *
35188 * <example name="select-unknown-value-required" module="staticSelect">
35189 * <file name="index.html">
35190 * <div ng-controller="ExampleController">
35191 * <form name="myForm">
35192 * <label for="testSelect"> Select: </label><br>
35193 * <select name="testSelect" ng-model="selected" required unknown-value-required>
35194 * <option value="option-1">Option 1</option>
35195 * <option value="option-2">Option 2</option>
35196 * </select><br>
35197 * <span class="error" ng-if="myForm.testSelect.$error.required">Error: Please select a value</span><br>
35198 *
35199 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
35200 * </form>
35201 * </div>
35202 * </file>
35203 * <file name="app.js">
35204 * angular.module('staticSelect', [])
35205 * .controller('ExampleController', ['$scope', function($scope) {
35206 * $scope.selected = null;
35207 *
35208 * $scope.forceUnknownOption = function() {
35209 * $scope.selected = 'nonsense';
35210 * };
35211 * }])
35212 * .directive('unknownValueRequired', function() {
35213 * return {
35214 * priority: 1, // This directive must run after the required directive has added its validator
35215 * require: ['ngModel', 'select'],
35216 * link: function(scope, element, attrs, ctrls) {
35217 * var ngModelCtrl = ctrls[0];
35218 * var selectCtrl = ctrls[1];
35219 *
35220 * var originalRequiredValidator = ngModelCtrl.$validators.required;
35221 *
35222 * ngModelCtrl.$validators.required = function() {
35223 * if (attrs.required && selectCtrl.$isUnknownOptionSelected()) {
35224 * return false;
35225 * }
35226 *
35227 * return originalRequiredValidator.apply(this, arguments);
35228 * };
35229 * }
35230 * };
35231 * });
35232 * </file>
35233 * <file name="protractor.js" type="protractor">
35234 * it('should show the error message when the unknown option is selected', function() {
35235
35236 var error = element(by.className('error'));
35237
35238 expect(error.getText()).toBe('Error: Please select a value');
35239
35240 element(by.cssContainingText('option', 'Option 1')).click();
35241
35242 expect(error.isPresent()).toBe(false);
35243
35244 element(by.tagName('button')).click();
35245
35246 expect(error.getText()).toBe('Error: Please select a value');
35247 });
35248 * </file>
35249 *</example>
35250 *
35251 *
35252 */
35253var SelectController =
35254 ['$element', '$scope', /** @this */ function($element, $scope) {
35255
35256 var self = this,
35257 optionsMap = new NgMap();
35258
35259 self.selectValueMap = {}; // Keys are the hashed values, values the original values
35260
35261 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
35262 self.ngModelCtrl = noopNgModelController;
35263 self.multiple = false;
35264
35265 // The "unknown" option is one that is prepended to the list if the viewValue
35266 // does not match any of the options. When it is rendered the value of the unknown
35267 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
35268 //
35269 // Support: IE 9 only
35270 // We can't just jqLite('<option>') since jqLite is not smart enough
35271 // to create it in <select> and IE barfs otherwise.
35272 self.unknownOption = jqLite(window.document.createElement('option'));
35273
35274 // The empty option is an option with the value '' that the application developer can
35275 // provide inside the select. It is always selectable and indicates that a "null" selection has
35276 // been made by the user.
35277 // If the select has an empty option, and the model of the select is set to "undefined" or "null",
35278 // the empty option is selected.
35279 // If the model is set to a different unmatched value, the unknown option is rendered and
35280 // selected, i.e both are present, because a "null" selection and an unknown value are different.
35281 self.hasEmptyOption = false;
35282 self.emptyOption = undefined;
35283
35284 self.renderUnknownOption = function(val) {
35285 var unknownVal = self.generateUnknownOptionValue(val);
35286 self.unknownOption.val(unknownVal);
35287 $element.prepend(self.unknownOption);
35288 setOptionSelectedStatus(self.unknownOption, true);
35289 $element.val(unknownVal);
35290 };
35291
35292 self.updateUnknownOption = function(val) {
35293 var unknownVal = self.generateUnknownOptionValue(val);
35294 self.unknownOption.val(unknownVal);
35295 setOptionSelectedStatus(self.unknownOption, true);
35296 $element.val(unknownVal);
35297 };
35298
35299 self.generateUnknownOptionValue = function(val) {
35300 return '? ' + hashKey(val) + ' ?';
35301 };
35302
35303 self.removeUnknownOption = function() {
35304 if (self.unknownOption.parent()) self.unknownOption.remove();
35305 };
35306
35307 self.selectEmptyOption = function() {
35308 if (self.emptyOption) {
35309 $element.val('');
35310 setOptionSelectedStatus(self.emptyOption, true);
35311 }
35312 };
35313
35314 self.unselectEmptyOption = function() {
35315 if (self.hasEmptyOption) {
35316 setOptionSelectedStatus(self.emptyOption, false);
35317 }
35318 };
35319
35320 $scope.$on('$destroy', function() {
35321 // disable unknown option so that we don't do work when the whole select is being destroyed
35322 self.renderUnknownOption = noop;
35323 });
35324
35325 // Read the value of the select control, the implementation of this changes depending
35326 // upon whether the select can have multiple values and whether ngOptions is at work.
35327 self.readValue = function readSingleValue() {
35328 var val = $element.val();
35329 // ngValue added option values are stored in the selectValueMap, normal interpolations are not
35330 var realVal = val in self.selectValueMap ? self.selectValueMap[val] : val;
35331
35332 if (self.hasOption(realVal)) {
35333 return realVal;
35334 }
35335
35336 return null;
35337 };
35338
35339
35340 // Write the value to the select control, the implementation of this changes depending
35341 // upon whether the select can have multiple values and whether ngOptions is at work.
35342 self.writeValue = function writeSingleValue(value) {
35343 // Make sure to remove the selected attribute from the previously selected option
35344 // Otherwise, screen readers might get confused
35345 var currentlySelectedOption = $element[0].options[$element[0].selectedIndex];
35346 if (currentlySelectedOption) setOptionSelectedStatus(jqLite(currentlySelectedOption), false);
35347
35348 if (self.hasOption(value)) {
35349 self.removeUnknownOption();
35350
35351 var hashedVal = hashKey(value);
35352 $element.val(hashedVal in self.selectValueMap ? hashedVal : value);
35353
35354 // Set selected attribute and property on selected option for screen readers
35355 var selectedOption = $element[0].options[$element[0].selectedIndex];
35356 setOptionSelectedStatus(jqLite(selectedOption), true);
35357 } else {
35358 self.selectUnknownOrEmptyOption(value);
35359 }
35360 };
35361
35362
35363 // Tell the select control that an option, with the given value, has been added
35364 self.addOption = function(value, element) {
35365 // Skip comment nodes, as they only pollute the `optionsMap`
35366 if (element[0].nodeType === NODE_TYPE_COMMENT) return;
35367
35368 assertNotHasOwnProperty(value, '"option value"');
35369 if (value === '') {
35370 self.hasEmptyOption = true;
35371 self.emptyOption = element;
35372 }
35373 var count = optionsMap.get(value) || 0;
35374 optionsMap.set(value, count + 1);
35375 // Only render at the end of a digest. This improves render performance when many options
35376 // are added during a digest and ensures all relevant options are correctly marked as selected
35377 scheduleRender();
35378 };
35379
35380 // Tell the select control that an option, with the given value, has been removed
35381 self.removeOption = function(value) {
35382 var count = optionsMap.get(value);
35383 if (count) {
35384 if (count === 1) {
35385 optionsMap.delete(value);
35386 if (value === '') {
35387 self.hasEmptyOption = false;
35388 self.emptyOption = undefined;
35389 }
35390 } else {
35391 optionsMap.set(value, count - 1);
35392 }
35393 }
35394 };
35395
35396 // Check whether the select control has an option matching the given value
35397 self.hasOption = function(value) {
35398 return !!optionsMap.get(value);
35399 };
35400
35401 /**
35402 * @ngdoc method
35403 * @name select.SelectController#$hasEmptyOption
35404 *
35405 * @description
35406 *
35407 * Returns `true` if the select element currently has an empty option
35408 * element, i.e. an option that signifies that the select is empty / the selection is null.
35409 *
35410 */
35411 self.$hasEmptyOption = function() {
35412 return self.hasEmptyOption;
35413 };
35414
35415 /**
35416 * @ngdoc method
35417 * @name select.SelectController#$isUnknownOptionSelected
35418 *
35419 * @description
35420 *
35421 * Returns `true` if the select element's unknown option is selected. The unknown option is added
35422 * and automatically selected whenever the select model doesn't match any option.
35423 *
35424 */
35425 self.$isUnknownOptionSelected = function() {
35426 // Presence of the unknown option means it is selected
35427 return $element[0].options[0] === self.unknownOption[0];
35428 };
35429
35430 /**
35431 * @ngdoc method
35432 * @name select.SelectController#$isEmptyOptionSelected
35433 *
35434 * @description
35435 *
35436 * Returns `true` if the select element has an empty option and this empty option is currently
35437 * selected. Returns `false` if the select element has no empty option or it is not selected.
35438 *
35439 */
35440 self.$isEmptyOptionSelected = function() {
35441 return self.hasEmptyOption && $element[0].options[$element[0].selectedIndex] === self.emptyOption[0];
35442 };
35443
35444 self.selectUnknownOrEmptyOption = function(value) {
35445 if (value == null && self.emptyOption) {
35446 self.removeUnknownOption();
35447 self.selectEmptyOption();
35448 } else if (self.unknownOption.parent().length) {
35449 self.updateUnknownOption(value);
35450 } else {
35451 self.renderUnknownOption(value);
35452 }
35453 };
35454
35455 var renderScheduled = false;
35456 function scheduleRender() {
35457 if (renderScheduled) return;
35458 renderScheduled = true;
35459 $scope.$$postDigest(function() {
35460 renderScheduled = false;
35461 self.ngModelCtrl.$render();
35462 });
35463 }
35464
35465 var updateScheduled = false;
35466 function scheduleViewValueUpdate(renderAfter) {
35467 if (updateScheduled) return;
35468
35469 updateScheduled = true;
35470
35471 $scope.$$postDigest(function() {
35472 if ($scope.$$destroyed) return;
35473
35474 updateScheduled = false;
35475 self.ngModelCtrl.$setViewValue(self.readValue());
35476 if (renderAfter) self.ngModelCtrl.$render();
35477 });
35478 }
35479
35480
35481 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
35482
35483 if (optionAttrs.$attr.ngValue) {
35484 // The value attribute is set by ngValue
35485 var oldVal, hashedVal;
35486 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
35487
35488 var removal;
35489 var previouslySelected = optionElement.prop('selected');
35490
35491 if (isDefined(hashedVal)) {
35492 self.removeOption(oldVal);
35493 delete self.selectValueMap[hashedVal];
35494 removal = true;
35495 }
35496
35497 hashedVal = hashKey(newVal);
35498 oldVal = newVal;
35499 self.selectValueMap[hashedVal] = newVal;
35500 self.addOption(newVal, optionElement);
35501 // Set the attribute directly instead of using optionAttrs.$set - this stops the observer
35502 // from firing a second time. Other $observers on value will also get the result of the
35503 // ngValue expression, not the hashed value
35504 optionElement.attr('value', hashedVal);
35505
35506 if (removal && previouslySelected) {
35507 scheduleViewValueUpdate();
35508 }
35509
35510 });
35511 } else if (interpolateValueFn) {
35512 // The value attribute is interpolated
35513 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
35514 // This method is overwritten in ngOptions and has side-effects!
35515 self.readValue();
35516
35517 var removal;
35518 var previouslySelected = optionElement.prop('selected');
35519
35520 if (isDefined(oldVal)) {
35521 self.removeOption(oldVal);
35522 removal = true;
35523 }
35524 oldVal = newVal;
35525 self.addOption(newVal, optionElement);
35526
35527 if (removal && previouslySelected) {
35528 scheduleViewValueUpdate();
35529 }
35530 });
35531 } else if (interpolateTextFn) {
35532 // The text content is interpolated
35533 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
35534 optionAttrs.$set('value', newVal);
35535 var previouslySelected = optionElement.prop('selected');
35536 if (oldVal !== newVal) {
35537 self.removeOption(oldVal);
35538 }
35539 self.addOption(newVal, optionElement);
35540
35541 if (oldVal && previouslySelected) {
35542 scheduleViewValueUpdate();
35543 }
35544 });
35545 } else {
35546 // The value attribute is static
35547 self.addOption(optionAttrs.value, optionElement);
35548 }
35549
35550
35551 optionAttrs.$observe('disabled', function(newVal) {
35552
35553 // Since model updates will also select disabled options (like ngOptions),
35554 // we only have to handle options becoming disabled, not enabled
35555
35556 if (newVal === 'true' || newVal && optionElement.prop('selected')) {
35557 if (self.multiple) {
35558 scheduleViewValueUpdate(true);
35559 } else {
35560 self.ngModelCtrl.$setViewValue(null);
35561 self.ngModelCtrl.$render();
35562 }
35563 }
35564 });
35565
35566 optionElement.on('$destroy', function() {
35567 var currentValue = self.readValue();
35568 var removeValue = optionAttrs.value;
35569
35570 self.removeOption(removeValue);
35571 scheduleRender();
35572
35573 if (self.multiple && currentValue && currentValue.indexOf(removeValue) !== -1 ||
35574 currentValue === removeValue
35575 ) {
35576 // When multiple (selected) options are destroyed at the same time, we don't want
35577 // to run a model update for each of them. Instead, run a single update in the $$postDigest
35578 scheduleViewValueUpdate(true);
35579 }
35580 });
35581 };
35582}];
35583
35584/**
35585 * @ngdoc directive
35586 * @name select
35587 * @restrict E
35588 *
35589 * @description
35590 * HTML `select` element with AngularJS data-binding.
35591 *
35592 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
35593 * between the scope and the `<select>` control (including setting default values).
35594 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
35595 * {@link ngOptions `ngOptions`} directives.
35596 *
35597 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
35598 * to the model identified by the `ngModel` directive. With static or repeated options, this is
35599 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
35600 * Value and textContent can be interpolated.
35601 *
35602 * The {@link select.SelectController select controller} exposes utility functions that can be used
35603 * to manipulate the select's behavior.
35604 *
35605 * ## Matching model and option values
35606 *
35607 * In general, the match between the model and an option is evaluated by strictly comparing the model
35608 * value against the value of the available options.
35609 *
35610 * If you are setting the option value with the option's `value` attribute, or textContent, the
35611 * value will always be a `string` which means that the model value must also be a string.
35612 * Otherwise the `select` directive cannot match them correctly.
35613 *
35614 * To bind the model to a non-string value, you can use one of the following strategies:
35615 * - the {@link ng.ngOptions `ngOptions`} directive
35616 * ({@link ng.select#using-select-with-ngoptions-and-setting-a-default-value})
35617 * - the {@link ng.ngValue `ngValue`} directive, which allows arbitrary expressions to be
35618 * option values ({@link ng.select#using-ngvalue-to-bind-the-model-to-an-array-of-objects Example})
35619 * - model $parsers / $formatters to convert the string value
35620 * ({@link ng.select#binding-select-to-a-non-string-value-via-ngmodel-parsing-formatting Example})
35621 *
35622 * If the viewValue of `ngModel` does not match any of the options, then the control
35623 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
35624 *
35625 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
35626 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
35627 * option. See example below for demonstration.
35628 *
35629 * ## Choosing between `ngRepeat` and `ngOptions`
35630 *
35631 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
35632 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits:
35633 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
35634 * comprehension expression
35635 * - reduced memory consumption by not creating a new scope for each repeated instance
35636 * - increased render speed by creating the options in a documentFragment instead of individually
35637 *
35638 * Specifically, select with repeated options slows down significantly starting at 2000 options in
35639 * Chrome and Internet Explorer / Edge.
35640 *
35641 *
35642 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
35643 * @param {string=} name Property name of the form under which the control is published.
35644 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
35645 * bound to the model as an array.
35646 * @param {string=} required Sets `required` validation error key if the value is not entered.
35647 * @param {string=} ngRequired Adds required attribute and required validation constraint to
35648 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
35649 * when you want to data-bind to the required attribute.
35650 * @param {string=} ngChange AngularJS expression to be executed when selected option(s) changes due to user
35651 * interaction with the select element.
35652 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
35653 * set on the model on selection. See {@link ngOptions `ngOptions`}.
35654 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the
35655 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
35656 *
35657 *
35658 * @example
35659 * ### Simple `select` elements with static options
35660 *
35661 * <example name="static-select" module="staticSelect">
35662 * <file name="index.html">
35663 * <div ng-controller="ExampleController">
35664 * <form name="myForm">
35665 * <label for="singleSelect"> Single select: </label><br>
35666 * <select name="singleSelect" ng-model="data.singleSelect">
35667 * <option value="option-1">Option 1</option>
35668 * <option value="option-2">Option 2</option>
35669 * </select><br>
35670 *
35671 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
35672 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
35673 * <option value="">---Please select---</option> <!-- not selected / blank option -->
35674 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
35675 * <option value="option-2">Option 2</option>
35676 * </select><br>
35677 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
35678 * <tt>singleSelect = {{data.singleSelect}}</tt>
35679 *
35680 * <hr>
35681 * <label for="multipleSelect"> Multiple select: </label><br>
35682 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
35683 * <option value="option-1">Option 1</option>
35684 * <option value="option-2">Option 2</option>
35685 * <option value="option-3">Option 3</option>
35686 * </select><br>
35687 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
35688 * </form>
35689 * </div>
35690 * </file>
35691 * <file name="app.js">
35692 * angular.module('staticSelect', [])
35693 * .controller('ExampleController', ['$scope', function($scope) {
35694 * $scope.data = {
35695 * singleSelect: null,
35696 * multipleSelect: [],
35697 * option1: 'option-1'
35698 * };
35699 *
35700 * $scope.forceUnknownOption = function() {
35701 * $scope.data.singleSelect = 'nonsense';
35702 * };
35703 * }]);
35704 * </file>
35705 *</example>
35706 *
35707 * @example
35708 * ### Using `ngRepeat` to generate `select` options
35709 * <example name="select-ngrepeat" module="ngrepeatSelect">
35710 * <file name="index.html">
35711 * <div ng-controller="ExampleController">
35712 * <form name="myForm">
35713 * <label for="repeatSelect"> Repeat select: </label>
35714 * <select name="repeatSelect" id="repeatSelect" ng-model="data.model">
35715 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
35716 * </select>
35717 * </form>
35718 * <hr>
35719 * <tt>model = {{data.model}}</tt><br/>
35720 * </div>
35721 * </file>
35722 * <file name="app.js">
35723 * angular.module('ngrepeatSelect', [])
35724 * .controller('ExampleController', ['$scope', function($scope) {
35725 * $scope.data = {
35726 * model: null,
35727 * availableOptions: [
35728 * {id: '1', name: 'Option A'},
35729 * {id: '2', name: 'Option B'},
35730 * {id: '3', name: 'Option C'}
35731 * ]
35732 * };
35733 * }]);
35734 * </file>
35735 *</example>
35736 *
35737 * @example
35738 * ### Using `ngValue` to bind the model to an array of objects
35739 * <example name="select-ngvalue" module="ngvalueSelect">
35740 * <file name="index.html">
35741 * <div ng-controller="ExampleController">
35742 * <form name="myForm">
35743 * <label for="ngvalueselect"> ngvalue select: </label>
35744 * <select size="6" name="ngvalueselect" ng-model="data.model" multiple>
35745 * <option ng-repeat="option in data.availableOptions" ng-value="option.value">{{option.name}}</option>
35746 * </select>
35747 * </form>
35748 * <hr>
35749 * <pre>model = {{data.model | json}}</pre><br/>
35750 * </div>
35751 * </file>
35752 * <file name="app.js">
35753 * angular.module('ngvalueSelect', [])
35754 * .controller('ExampleController', ['$scope', function($scope) {
35755 * $scope.data = {
35756 * model: null,
35757 * availableOptions: [
35758 {value: 'myString', name: 'string'},
35759 {value: 1, name: 'integer'},
35760 {value: true, name: 'boolean'},
35761 {value: null, name: 'null'},
35762 {value: {prop: 'value'}, name: 'object'},
35763 {value: ['a'], name: 'array'}
35764 * ]
35765 * };
35766 * }]);
35767 * </file>
35768 *</example>
35769 *
35770 * @example
35771 * ### Using `select` with `ngOptions` and setting a default value
35772 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
35773 *
35774 * <example name="select-with-default-values" module="defaultValueSelect">
35775 * <file name="index.html">
35776 * <div ng-controller="ExampleController">
35777 * <form name="myForm">
35778 * <label for="mySelect">Make a choice:</label>
35779 * <select name="mySelect" id="mySelect"
35780 * ng-options="option.name for option in data.availableOptions track by option.id"
35781 * ng-model="data.selectedOption"></select>
35782 * </form>
35783 * <hr>
35784 * <tt>option = {{data.selectedOption}}</tt><br/>
35785 * </div>
35786 * </file>
35787 * <file name="app.js">
35788 * angular.module('defaultValueSelect', [])
35789 * .controller('ExampleController', ['$scope', function($scope) {
35790 * $scope.data = {
35791 * availableOptions: [
35792 * {id: '1', name: 'Option A'},
35793 * {id: '2', name: 'Option B'},
35794 * {id: '3', name: 'Option C'}
35795 * ],
35796 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
35797 * };
35798 * }]);
35799 * </file>
35800 *</example>
35801 *
35802 * @example
35803 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
35804 *
35805 * <example name="select-with-non-string-options" module="nonStringSelect">
35806 * <file name="index.html">
35807 * <select ng-model="model.id" convert-to-number>
35808 * <option value="0">Zero</option>
35809 * <option value="1">One</option>
35810 * <option value="2">Two</option>
35811 * </select>
35812 * {{ model }}
35813 * </file>
35814 * <file name="app.js">
35815 * angular.module('nonStringSelect', [])
35816 * .run(function($rootScope) {
35817 * $rootScope.model = { id: 2 };
35818 * })
35819 * .directive('convertToNumber', function() {
35820 * return {
35821 * require: 'ngModel',
35822 * link: function(scope, element, attrs, ngModel) {
35823 * ngModel.$parsers.push(function(val) {
35824 * return parseInt(val, 10);
35825 * });
35826 * ngModel.$formatters.push(function(val) {
35827 * return '' + val;
35828 * });
35829 * }
35830 * };
35831 * });
35832 * </file>
35833 * <file name="protractor.js" type="protractor">
35834 * it('should initialize to model', function() {
35835 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
35836 * });
35837 * </file>
35838 * </example>
35839 *
35840 */
35841var selectDirective = function() {
35842
35843 return {
35844 restrict: 'E',
35845 require: ['select', '?ngModel'],
35846 controller: SelectController,
35847 priority: 1,
35848 link: {
35849 pre: selectPreLink,
35850 post: selectPostLink
35851 }
35852 };
35853
35854 function selectPreLink(scope, element, attr, ctrls) {
35855
35856 var selectCtrl = ctrls[0];
35857 var ngModelCtrl = ctrls[1];
35858
35859 // if ngModel is not defined, we don't need to do anything but set the registerOption
35860 // function to noop, so options don't get added internally
35861 if (!ngModelCtrl) {
35862 selectCtrl.registerOption = noop;
35863 return;
35864 }
35865
35866
35867 selectCtrl.ngModelCtrl = ngModelCtrl;
35868
35869 // When the selected item(s) changes we delegate getting the value of the select control
35870 // to the `readValue` method, which can be changed if the select can have multiple
35871 // selected values or if the options are being generated by `ngOptions`
35872 element.on('change', function() {
35873 selectCtrl.removeUnknownOption();
35874 scope.$apply(function() {
35875 ngModelCtrl.$setViewValue(selectCtrl.readValue());
35876 });
35877 });
35878
35879 // If the select allows multiple values then we need to modify how we read and write
35880 // values from and to the control; also what it means for the value to be empty and
35881 // we have to add an extra watch since ngModel doesn't work well with arrays - it
35882 // doesn't trigger rendering if only an item in the array changes.
35883 if (attr.multiple) {
35884 selectCtrl.multiple = true;
35885
35886 // Read value now needs to check each option to see if it is selected
35887 selectCtrl.readValue = function readMultipleValue() {
35888 var array = [];
35889 forEach(element.find('option'), function(option) {
35890 if (option.selected && !option.disabled) {
35891 var val = option.value;
35892 array.push(val in selectCtrl.selectValueMap ? selectCtrl.selectValueMap[val] : val);
35893 }
35894 });
35895 return array;
35896 };
35897
35898 // Write value now needs to set the selected property of each matching option
35899 selectCtrl.writeValue = function writeMultipleValue(value) {
35900 forEach(element.find('option'), function(option) {
35901 var shouldBeSelected = !!value && (includes(value, option.value) ||
35902 includes(value, selectCtrl.selectValueMap[option.value]));
35903 var currentlySelected = option.selected;
35904
35905 // Support: IE 9-11 only, Edge 12-15+
35906 // In IE and Edge adding options to the selection via shift+click/UP/DOWN
35907 // will de-select already selected options if "selected" on those options was set
35908 // more than once (i.e. when the options were already selected)
35909 // So we only modify the selected property if necessary.
35910 // Note: this behavior cannot be replicated via unit tests because it only shows in the
35911 // actual user interface.
35912 if (shouldBeSelected !== currentlySelected) {
35913 setOptionSelectedStatus(jqLite(option), shouldBeSelected);
35914 }
35915
35916 });
35917 };
35918
35919 // we have to do it on each watch since ngModel watches reference, but
35920 // we need to work of an array, so we need to see if anything was inserted/removed
35921 var lastView, lastViewRef = NaN;
35922 scope.$watch(function selectMultipleWatch() {
35923 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
35924 lastView = shallowCopy(ngModelCtrl.$viewValue);
35925 ngModelCtrl.$render();
35926 }
35927 lastViewRef = ngModelCtrl.$viewValue;
35928 });
35929
35930 // If we are a multiple select then value is now a collection
35931 // so the meaning of $isEmpty changes
35932 ngModelCtrl.$isEmpty = function(value) {
35933 return !value || value.length === 0;
35934 };
35935
35936 }
35937 }
35938
35939 function selectPostLink(scope, element, attrs, ctrls) {
35940 // if ngModel is not defined, we don't need to do anything
35941 var ngModelCtrl = ctrls[1];
35942 if (!ngModelCtrl) return;
35943
35944 var selectCtrl = ctrls[0];
35945
35946 // We delegate rendering to the `writeValue` method, which can be changed
35947 // if the select can have multiple selected values or if the options are being
35948 // generated by `ngOptions`.
35949 // This must be done in the postLink fn to prevent $render to be called before
35950 // all nodes have been linked correctly.
35951 ngModelCtrl.$render = function() {
35952 selectCtrl.writeValue(ngModelCtrl.$viewValue);
35953 };
35954 }
35955};
35956
35957
35958// The option directive is purely designed to communicate the existence (or lack of)
35959// of dynamically created (and destroyed) option elements to their containing select
35960// directive via its controller.
35961var optionDirective = ['$interpolate', function($interpolate) {
35962 return {
35963 restrict: 'E',
35964 priority: 100,
35965 compile: function(element, attr) {
35966 var interpolateValueFn, interpolateTextFn;
35967
35968 if (isDefined(attr.ngValue)) {
35969 // Will be handled by registerOption
35970 } else if (isDefined(attr.value)) {
35971 // If the value attribute is defined, check if it contains an interpolation
35972 interpolateValueFn = $interpolate(attr.value, true);
35973 } else {
35974 // If the value attribute is not defined then we fall back to the
35975 // text content of the option element, which may be interpolated
35976 interpolateTextFn = $interpolate(element.text(), true);
35977 if (!interpolateTextFn) {
35978 attr.$set('value', element.text());
35979 }
35980 }
35981
35982 return function(scope, element, attr) {
35983 // This is an optimization over using ^^ since we don't want to have to search
35984 // all the way to the root of the DOM for every single option element
35985 var selectCtrlName = '$selectController',
35986 parent = element.parent(),
35987 selectCtrl = parent.data(selectCtrlName) ||
35988 parent.parent().data(selectCtrlName); // in case we are in optgroup
35989
35990 if (selectCtrl) {
35991 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
35992 }
35993 };
35994 }
35995 };
35996}];
35997
35998/**
35999 * @ngdoc directive
36000 * @name ngRequired
36001 * @restrict A
36002 *
36003 * @param {expression} ngRequired AngularJS expression. If it evaluates to `true`, it sets the
36004 * `required` attribute to the element and adds the `required`
36005 * {@link ngModel.NgModelController#$validators `validator`}.
36006 *
36007 * @description
36008 *
36009 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
36010 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be
36011 * applied to custom controls.
36012 *
36013 * The directive sets the `required` attribute on the element if the AngularJS expression inside
36014 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we
36015 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide}
36016 * for more info.
36017 *
36018 * The validator will set the `required` error key to true if the `required` attribute is set and
36019 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the
36020 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the
36021 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing
36022 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based.
36023 *
36024 * @example
36025 * <example name="ngRequiredDirective" module="ngRequiredExample">
36026 * <file name="index.html">
36027 * <script>
36028 * angular.module('ngRequiredExample', [])
36029 * .controller('ExampleController', ['$scope', function($scope) {
36030 * $scope.required = true;
36031 * }]);
36032 * </script>
36033 * <div ng-controller="ExampleController">
36034 * <form name="form">
36035 * <label for="required">Toggle required: </label>
36036 * <input type="checkbox" ng-model="required" id="required" />
36037 * <br>
36038 * <label for="input">This input must be filled if `required` is true: </label>
36039 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br>
36040 * <hr>
36041 * required error set? = <code>{{form.input.$error.required}}</code><br>
36042 * model = <code>{{model}}</code>
36043 * </form>
36044 * </div>
36045 * </file>
36046 * <file name="protractor.js" type="protractor">
36047 var required = element(by.binding('form.input.$error.required'));
36048 var model = element(by.binding('model'));
36049 var input = element(by.id('input'));
36050
36051 it('should set the required error', function() {
36052 expect(required.getText()).toContain('true');
36053
36054 input.sendKeys('123');
36055 expect(required.getText()).not.toContain('true');
36056 expect(model.getText()).toContain('123');
36057 });
36058 * </file>
36059 * </example>
36060 */
36061var requiredDirective = ['$parse', function($parse) {
36062 return {
36063 restrict: 'A',
36064 require: '?ngModel',
36065 link: function(scope, elm, attr, ctrl) {
36066 if (!ctrl) return;
36067 // For boolean attributes like required, presence means true
36068 var value = attr.hasOwnProperty('required') || $parse(attr.ngRequired)(scope);
36069
36070 if (!attr.ngRequired) {
36071 // force truthy in case we are on non input element
36072 // (input elements do this automatically for boolean attributes like required)
36073 attr.required = true;
36074 }
36075
36076 ctrl.$validators.required = function(modelValue, viewValue) {
36077 return !value || !ctrl.$isEmpty(viewValue);
36078 };
36079
36080 attr.$observe('required', function(newVal) {
36081
36082 if (value !== newVal) {
36083 value = newVal;
36084 ctrl.$validate();
36085 }
36086 });
36087 }
36088 };
36089}];
36090
36091/**
36092 * @ngdoc directive
36093 * @name ngPattern
36094 * @restrict A
36095 *
36096 * @param {expression|RegExp} ngPattern AngularJS expression that must evaluate to a `RegExp` or a `String`
36097 * parsable into a `RegExp`, or a `RegExp` literal. See above for
36098 * more details.
36099 *
36100 * @description
36101 *
36102 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
36103 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
36104 *
36105 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
36106 * does not match a RegExp which is obtained from the `ngPattern` attribute value:
36107 * - the value is an AngularJS expression:
36108 * - If the expression evaluates to a RegExp object, then this is used directly.
36109 * - If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it
36110 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
36111 * - If the value is a RegExp literal, e.g. `ngPattern="/^\d+$/"`, it is used directly.
36112 *
36113 * <div class="alert alert-info">
36114 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
36115 * start at the index of the last search's match, thus not taking the whole input value into
36116 * account.
36117 * </div>
36118 *
36119 * <div class="alert alert-info">
36120 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two
36121 * differences:
36122 * <ol>
36123 * <li>
36124 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is
36125 * not available.
36126 * </li>
36127 * <li>
36128 * The `ngPattern` attribute must be an expression, while the `pattern` value must be
36129 * interpolated.
36130 * </li>
36131 * </ol>
36132 * </div>
36133 *
36134 * @example
36135 * <example name="ngPatternDirective" module="ngPatternExample">
36136 * <file name="index.html">
36137 * <script>
36138 * angular.module('ngPatternExample', [])
36139 * .controller('ExampleController', ['$scope', function($scope) {
36140 * $scope.regex = '\\d+';
36141 * }]);
36142 * </script>
36143 * <div ng-controller="ExampleController">
36144 * <form name="form">
36145 * <label for="regex">Set a pattern (regex string): </label>
36146 * <input type="text" ng-model="regex" id="regex" />
36147 * <br>
36148 * <label for="input">This input is restricted by the current pattern: </label>
36149 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br>
36150 * <hr>
36151 * input valid? = <code>{{form.input.$valid}}</code><br>
36152 * model = <code>{{model}}</code>
36153 * </form>
36154 * </div>
36155 * </file>
36156 * <file name="protractor.js" type="protractor">
36157 var model = element(by.binding('model'));
36158 var input = element(by.id('input'));
36159
36160 it('should validate the input with the default pattern', function() {
36161 input.sendKeys('aaa');
36162 expect(model.getText()).not.toContain('aaa');
36163
36164 input.clear().then(function() {
36165 input.sendKeys('123');
36166 expect(model.getText()).toContain('123');
36167 });
36168 });
36169 * </file>
36170 * </example>
36171 */
36172var patternDirective = ['$parse', function($parse) {
36173 return {
36174 restrict: 'A',
36175 require: '?ngModel',
36176 compile: function(tElm, tAttr) {
36177 var patternExp;
36178 var parseFn;
36179
36180 if (tAttr.ngPattern) {
36181 patternExp = tAttr.ngPattern;
36182
36183 // ngPattern might be a scope expression, or an inlined regex, which is not parsable.
36184 // We get value of the attribute here, so we can compare the old and the new value
36185 // in the observer to avoid unnecessary validations
36186 if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) {
36187 parseFn = function() { return tAttr.ngPattern; };
36188 } else {
36189 parseFn = $parse(tAttr.ngPattern);
36190 }
36191 }
36192
36193 return function(scope, elm, attr, ctrl) {
36194 if (!ctrl) return;
36195
36196 var attrVal = attr.pattern;
36197
36198 if (attr.ngPattern) {
36199 attrVal = parseFn(scope);
36200 } else {
36201 patternExp = attr.pattern;
36202 }
36203
36204 var regexp = parsePatternAttr(attrVal, patternExp, elm);
36205
36206 attr.$observe('pattern', function(newVal) {
36207 var oldRegexp = regexp;
36208
36209 regexp = parsePatternAttr(newVal, patternExp, elm);
36210
36211 if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) {
36212 ctrl.$validate();
36213 }
36214 });
36215
36216 ctrl.$validators.pattern = function(modelValue, viewValue) {
36217 // HTML5 pattern constraint validates the input value, so we validate the viewValue
36218 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
36219 };
36220 };
36221 }
36222
36223 };
36224}];
36225
36226/**
36227 * @ngdoc directive
36228 * @name ngMaxlength
36229 * @restrict A
36230 *
36231 * @param {expression} ngMaxlength AngularJS expression that must evaluate to a `Number` or `String`
36232 * parsable into a `Number`. Used as value for the `maxlength`
36233 * {@link ngModel.NgModelController#$validators validator}.
36234 *
36235 * @description
36236 *
36237 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
36238 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
36239 *
36240 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
36241 * is longer than the integer obtained by evaluating the AngularJS expression given in the
36242 * `ngMaxlength` attribute value.
36243 *
36244 * <div class="alert alert-info">
36245 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two
36246 * differences:
36247 * <ol>
36248 * <li>
36249 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint
36250 * validation is not available.
36251 * </li>
36252 * <li>
36253 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be
36254 * interpolated.
36255 * </li>
36256 * </ol>
36257 * </div>
36258 *
36259 * @example
36260 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample">
36261 * <file name="index.html">
36262 * <script>
36263 * angular.module('ngMaxlengthExample', [])
36264 * .controller('ExampleController', ['$scope', function($scope) {
36265 * $scope.maxlength = 5;
36266 * }]);
36267 * </script>
36268 * <div ng-controller="ExampleController">
36269 * <form name="form">
36270 * <label for="maxlength">Set a maxlength: </label>
36271 * <input type="number" ng-model="maxlength" id="maxlength" />
36272 * <br>
36273 * <label for="input">This input is restricted by the current maxlength: </label>
36274 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br>
36275 * <hr>
36276 * input valid? = <code>{{form.input.$valid}}</code><br>
36277 * model = <code>{{model}}</code>
36278 * </form>
36279 * </div>
36280 * </file>
36281 * <file name="protractor.js" type="protractor">
36282 var model = element(by.binding('model'));
36283 var input = element(by.id('input'));
36284
36285 it('should validate the input with the default maxlength', function() {
36286 input.sendKeys('abcdef');
36287 expect(model.getText()).not.toContain('abcdef');
36288
36289 input.clear().then(function() {
36290 input.sendKeys('abcde');
36291 expect(model.getText()).toContain('abcde');
36292 });
36293 });
36294 * </file>
36295 * </example>
36296 */
36297var maxlengthDirective = ['$parse', function($parse) {
36298 return {
36299 restrict: 'A',
36300 require: '?ngModel',
36301 link: function(scope, elm, attr, ctrl) {
36302 if (!ctrl) return;
36303
36304 var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope);
36305 var maxlengthParsed = parseLength(maxlength);
36306
36307 attr.$observe('maxlength', function(value) {
36308 if (maxlength !== value) {
36309 maxlengthParsed = parseLength(value);
36310 maxlength = value;
36311 ctrl.$validate();
36312 }
36313 });
36314 ctrl.$validators.maxlength = function(modelValue, viewValue) {
36315 return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed);
36316 };
36317 }
36318 };
36319}];
36320
36321/**
36322 * @ngdoc directive
36323 * @name ngMinlength
36324 * @restrict A
36325 *
36326 * @param {expression} ngMinlength AngularJS expression that must evaluate to a `Number` or `String`
36327 * parsable into a `Number`. Used as value for the `minlength`
36328 * {@link ngModel.NgModelController#$validators validator}.
36329 *
36330 * @description
36331 *
36332 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
36333 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
36334 *
36335 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
36336 * is shorter than the integer obtained by evaluating the AngularJS expression given in the
36337 * `ngMinlength` attribute value.
36338 *
36339 * <div class="alert alert-info">
36340 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two
36341 * differences:
36342 * <ol>
36343 * <li>
36344 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint
36345 * validation is not available.
36346 * </li>
36347 * <li>
36348 * The `ngMinlength` value must be an expression, while the `minlength` value must be
36349 * interpolated.
36350 * </li>
36351 * </ol>
36352 * </div>
36353 *
36354 * @example
36355 * <example name="ngMinlengthDirective" module="ngMinlengthExample">
36356 * <file name="index.html">
36357 * <script>
36358 * angular.module('ngMinlengthExample', [])
36359 * .controller('ExampleController', ['$scope', function($scope) {
36360 * $scope.minlength = 3;
36361 * }]);
36362 * </script>
36363 * <div ng-controller="ExampleController">
36364 * <form name="form">
36365 * <label for="minlength">Set a minlength: </label>
36366 * <input type="number" ng-model="minlength" id="minlength" />
36367 * <br>
36368 * <label for="input">This input is restricted by the current minlength: </label>
36369 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br>
36370 * <hr>
36371 * input valid? = <code>{{form.input.$valid}}</code><br>
36372 * model = <code>{{model}}</code>
36373 * </form>
36374 * </div>
36375 * </file>
36376 * <file name="protractor.js" type="protractor">
36377 var model = element(by.binding('model'));
36378 var input = element(by.id('input'));
36379
36380 it('should validate the input with the default minlength', function() {
36381 input.sendKeys('ab');
36382 expect(model.getText()).not.toContain('ab');
36383
36384 input.sendKeys('abc');
36385 expect(model.getText()).toContain('abc');
36386 });
36387 * </file>
36388 * </example>
36389 */
36390var minlengthDirective = ['$parse', function($parse) {
36391 return {
36392 restrict: 'A',
36393 require: '?ngModel',
36394 link: function(scope, elm, attr, ctrl) {
36395 if (!ctrl) return;
36396
36397 var minlength = attr.minlength || $parse(attr.ngMinlength)(scope);
36398 var minlengthParsed = parseLength(minlength) || -1;
36399
36400 attr.$observe('minlength', function(value) {
36401 if (minlength !== value) {
36402 minlengthParsed = parseLength(value) || -1;
36403 minlength = value;
36404 ctrl.$validate();
36405 }
36406
36407 });
36408 ctrl.$validators.minlength = function(modelValue, viewValue) {
36409 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed;
36410 };
36411 }
36412 };
36413}];
36414
36415
36416function parsePatternAttr(regex, patternExp, elm) {
36417 if (!regex) return undefined;
36418
36419 if (isString(regex)) {
36420 regex = new RegExp('^' + regex + '$');
36421 }
36422
36423 if (!regex.test) {
36424 throw minErr('ngPattern')('noregexp',
36425 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
36426 regex, startingTag(elm));
36427 }
36428
36429 return regex;
36430}
36431
36432function parseLength(val) {
36433 var intVal = toInt(val);
36434 return isNumberNaN(intVal) ? -1 : intVal;
36435}
36436
36437if (window.angular.bootstrap) {
36438 // AngularJS is already loaded, so we can return here...
36439 if (window.console) {
36440 console.log('WARNING: Tried to load AngularJS more than once.');
36441 }
36442 return;
36443}
36444
36445// try to bind to jquery now so that one can write jqLite(fn)
36446// but we will rebind on bootstrap again.
36447bindJQuery();
36448
36449publishExternalAPI(angular);
36450
36451angular.module("ngLocale", [], ["$provide", function($provide) {
36452var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
36453function getDecimals(n) {
36454 n = n + '';
36455 var i = n.indexOf('.');
36456 return (i == -1) ? 0 : n.length - i - 1;
36457}
36458
36459function getVF(n, opt_precision) {
36460 var v = opt_precision;
36461
36462 if (undefined === v) {
36463 v = Math.min(getDecimals(n), 3);
36464 }
36465
36466 var base = Math.pow(10, v);
36467 var f = ((n * base) | 0) % base;
36468 return {v: v, f: f};
36469}
36470
36471$provide.value("$locale", {
36472 "DATETIME_FORMATS": {
36473 "AMPMS": [
36474 "AM",
36475 "PM"
36476 ],
36477 "DAY": [
36478 "Sunday",
36479 "Monday",
36480 "Tuesday",
36481 "Wednesday",
36482 "Thursday",
36483 "Friday",
36484 "Saturday"
36485 ],
36486 "ERANAMES": [
36487 "Before Christ",
36488 "Anno Domini"
36489 ],
36490 "ERAS": [
36491 "BC",
36492 "AD"
36493 ],
36494 "FIRSTDAYOFWEEK": 6,
36495 "MONTH": [
36496 "January",
36497 "February",
36498 "March",
36499 "April",
36500 "May",
36501 "June",
36502 "July",
36503 "August",
36504 "September",
36505 "October",
36506 "November",
36507 "December"
36508 ],
36509 "SHORTDAY": [
36510 "Sun",
36511 "Mon",
36512 "Tue",
36513 "Wed",
36514 "Thu",
36515 "Fri",
36516 "Sat"
36517 ],
36518 "SHORTMONTH": [
36519 "Jan",
36520 "Feb",
36521 "Mar",
36522 "Apr",
36523 "May",
36524 "Jun",
36525 "Jul",
36526 "Aug",
36527 "Sep",
36528 "Oct",
36529 "Nov",
36530 "Dec"
36531 ],
36532 "STANDALONEMONTH": [
36533 "January",
36534 "February",
36535 "March",
36536 "April",
36537 "May",
36538 "June",
36539 "July",
36540 "August",
36541 "September",
36542 "October",
36543 "November",
36544 "December"
36545 ],
36546 "WEEKENDRANGE": [
36547 5,
36548 6
36549 ],
36550 "fullDate": "EEEE, MMMM d, y",
36551 "longDate": "MMMM d, y",
36552 "medium": "MMM d, y h:mm:ss a",
36553 "mediumDate": "MMM d, y",
36554 "mediumTime": "h:mm:ss a",
36555 "short": "M/d/yy h:mm a",
36556 "shortDate": "M/d/yy",
36557 "shortTime": "h:mm a"
36558 },
36559 "NUMBER_FORMATS": {
36560 "CURRENCY_SYM": "$",
36561 "DECIMAL_SEP": ".",
36562 "GROUP_SEP": ",",
36563 "PATTERNS": [
36564 {
36565 "gSize": 3,
36566 "lgSize": 3,
36567 "maxFrac": 3,
36568 "minFrac": 0,
36569 "minInt": 1,
36570 "negPre": "-",
36571 "negSuf": "",
36572 "posPre": "",
36573 "posSuf": ""
36574 },
36575 {
36576 "gSize": 3,
36577 "lgSize": 3,
36578 "maxFrac": 2,
36579 "minFrac": 2,
36580 "minInt": 1,
36581 "negPre": "-\u00a4",
36582 "negSuf": "",
36583 "posPre": "\u00a4",
36584 "posSuf": ""
36585 }
36586 ]
36587 },
36588 "id": "en-us",
36589 "localeID": "en_US",
36590 "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
36591});
36592}]);
36593
36594 jqLite(function() {
36595 angularInit(window.document, bootstrap);
36596 });
36597
36598})(window);
36599
36600!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend(window.angular.element('<style>').text('@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}'));
\No newline at end of file